commit ec72c5d62bd210cc83215711f2a954a3c93581c3 Author: matt Date: Thu Sep 18 22:20:01 2025 +0200 Initial import of HarborSmith website diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ddc12c7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,49 @@ +# Dependencies +node_modules +npm-debug.log +yarn-error.log +.pnpm-store + +# Next.js +.next +out +build +dist + +# Testing +coverage +.nyc_output + +# Environment +.env +.env.* +!.env.example + +# Git +.git +.gitignore +.gitea + +# Documentation +*.md +docs + +# IDE +.vscode +.idea +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Development +.docker +docker-compose.dev.yml +docker-compose.test.yml + +# Temporary files +tmp +temp +*.tmp diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..3bab863 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,31 @@ +name: build-website +on: + push: + branches: + - main + +jobs: + build: + runs-on: self-hosted + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: npm ci + working-directory: apps/website + + - name: Build Nuxt + run: npm run build + working-directory: apps/website + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: website-dist + path: apps/website/.output/public diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c380cb5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Dependencies +node_modules/ +**/node_modules/ + +# Build artifacts +**/.nuxt/ +**/.output/ +**/dist/ + +# Logs & temp +**/npm-debug.log* +**/yarn-error.log* +**/.DS_Store +**/Thumbs.db + +# Environment +.env +.env.* + +# IDE +.vscode/ +.idea/ + +# Misc +.DS_Store + +# AI helper artifacts +.claude/ +.playwright-mcp/ +.serena/ +Website-PDF-Mockups/ + +apps/website/nul + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b7d656b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,662 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## ๐Ÿšจ CRITICAL: MANDATORY MCP SERVER USAGE ๐Ÿšจ + +**YOU MUST USE MCP SERVERS PROACTIVELY AND EXTENSIVELY**. This codebase requires heavy use of MCP tools for all operations. Never work blind - always use the appropriate MCP servers BEFORE making any decisions or changes. + +### โšก INSTANT MCP TRIGGERS FOR HARBORSMITH + +**These MUST happen automatically without asking:** + +| User Says | Immediate MCP Response | Full Chain | +|-----------|----------------------|------------| +| "charter" or "booking" | `Serena:find_symbol("*charter*")` | โ†’ Zen:analyze โ†’ Edit โ†’ Test | +| "yacht" or "vessel" | `Serena:search_for_pattern("yacht\|vessel")` | โ†’ Understand โ†’ Modify โ†’ Verify | +| "payment" or "stripe" | `Context7:resolve-library-id("stripe")` | โ†’ Docs โ†’ Implement โ†’ Review | +| "auth" or "login" | `Zen:secaudit` + `Serena:find_symbol("*auth*")` | โ†’ Security check โ†’ Fix โ†’ Test | +| "slow" or "performance" | `Browser-tools:runPerformanceAudit()` | โ†’ Zen:analyze โ†’ Optimize โ†’ Verify | +| "error" or "bug" | `Zen:debug` + `Browser-tools:getConsoleErrors()` | โ†’ Investigate โ†’ Fix โ†’ Validate | +| "deploy" | `Zen:precommit` | โ†’ Validate โ†’ Build โ†’ Deploy | +| "test" | `Zen:testgen` + `Playwright:automate` | โ†’ Generate โ†’ Run โ†’ Report | +| ANY code edit | `IDE:getDiagnostics()` | โ†’ Fix errors โ†’ Zen:review | +| New feature | `Zen:planner` | โ†’ Plan โ†’ Research โ†’ Implement | + +### ๐ŸŽฏ MCP Server Quick Reference + +**Copy-paste these patterns:** + +```typescript +// Debug pattern +await zen.debug({ step: "Investigating charter booking issue", confidence: "medium" }) +await browserTools.getConsoleErrors() +await serena.find_symbol("CharterBooking") + +// Review pattern +await zen.codereview({ relevant_files: ["apps/api/*"], review_type: "security" }) + +// Performance pattern +await browserTools.runPerformanceAudit() +await zen.thinkdeep({ focus_areas: ["performance"] }) +await serena.search_for_pattern("SELECT.*FROM", context_lines_after: 5) +``` + +### Available MCP Servers and When to Use Them + +#### 1. **Zen** - ALWAYS USE FOR COMPLEX TASKS +- **MANDATORY for**: Debugging, code review, architecture decisions, planning +- **Tools**: `thinkdeep`, `debug`, `codereview`, `consensus`, `planner`, `precommit` +- **Usage**: ANY complex problem, architecture decision, or code quality check +- **Example**: `mcp__zen__debug` for any bug, `mcp__zen__codereview` after writing code + +#### 2. **Serena** - ALWAYS USE FOR CODE NAVIGATION +- **MANDATORY for**: Reading/understanding code, refactoring, symbol search +- **Tools**: `find_symbol`, `get_symbols_overview`, `search_for_pattern`, `replace_symbol_body` +- **Usage**: NEVER read files directly without first using Serena's semantic tools +- **Example**: Always use `find_symbol` before editing, `get_symbols_overview` before understanding a file + +#### 3. **Context7** - ALWAYS USE FOR LIBRARIES +- **MANDATORY for**: Any external library usage or documentation +- **Tools**: `resolve-library-id`, `get-library-docs` +- **Usage**: Before implementing ANY library feature, get its documentation +- **Example**: Using Stripe? First: `resolve-library-id("stripe")` then `get-library-docs` + +#### 4. **Playwright** - USE FOR ALL UI TESTING +- **Tools**: `browser_navigate`, `browser_click`, `browser_snapshot`, `browser_fill_form` +- **Usage**: Testing user flows, capturing UI state, form validation + +#### 5. **21st-dev Magic** - USE FOR UI COMPONENTS +- **Tools**: `21st_magic_component_builder`, `logo_search` +- **Usage**: When user requests UI components or logos + +### MCP Usage Rules + +1. **ALWAYS chain MCP tools** - One tool's output should trigger the next +2. **NEVER make assumptions** - Use tools to verify everything +3. **Parallel execution** - Run independent MCP calls simultaneously +4. **Use Zen for thinking** - Complex reasoning MUST go through Zen +5. **Verify with tools** - After changes, ALWAYS run verification tools + +## Project Overview + +**Harborsmith** is a comprehensive yacht charter and maintenance management platform for the San Francisco Bay Area, built with a microservices architecture using Docker containers behind nginx. + +### Technology Stack + +**Frontend:** +- Nuxt 3 (SSG for website, SPA for webapp, SSR/SPA for admin portal) +- Tailwind CSS v4, Nuxt UI v3, Tremor (dashboards), Motion.dev (animations) +- Pinia (state management), VueUse (utilities) +- Uppy (file uploads, 10GB+ support), hls.js (video streaming) + +**Backend:** +- Fastify + tRPC (type-safe APIs) +- Prisma ORM with PostgreSQL 16 +- BullMQ (job queues), Socket.io (real-time) +- Redis (caching), PgBouncer (connection pooling) + +**Infrastructure:** +- Docker Compose orchestration +- nginx reverse proxy (host level) +- External MinIO for object storage +- Keycloak (authentication), Directus (CMS) +- Multiple service replicas for high availability + +## Architecture Pattern + +``` +nginx (host) โ†’ Docker Containers โ†’ Services + โ”œโ”€โ”€ API (3 replicas) โ†’ PostgreSQL/Redis + โ”œโ”€โ”€ Website (2 replicas) + โ”œโ”€โ”€ WebApp (2 replicas) + โ””โ”€โ”€ Portal (2 replicas) +``` + +## Essential Commands + +```bash +# Start all services +docker-compose up -d + +# Stop all services +docker-compose down + +# View logs +docker-compose logs -f [service_name] + +# Run database migrations +docker-compose run --rm api npx prisma migrate deploy + +# Database operations +docker exec harborsmith_postgres pg_dump -U $DB_USER harborsmith > backup.sql +docker exec -i harborsmith_postgres psql -U $DB_USER harborsmith < backup.sql + +# Deploy to production +./deploy.sh + +# Check service health +./scripts/wait-for-healthy.sh + +# Rebuild specific service +docker-compose up -d --no-deps --build api-1 +``` + +## Service Ports + +- **3000, 3010, 3020**: API replicas +- **3001-3002**: Website replicas +- **3003-3004**: WebApp replicas +- **3005-3006**: Portal replicas +- **5432**: PostgreSQL (via PgBouncer on 6432) +- **6379**: Redis +- **9000**: MinIO (external) +- **8080**: Keycloak + +## Environment Configuration + +Critical environment variables that MUST be set: +```bash +# Database +DB_USER=harborsmith +DB_PASSWORD= + +# External MinIO (already running separately) +MINIO_ENDPOINT= +MINIO_PORT=9000 +MINIO_USE_SSL=true +MINIO_ACCESS_KEY= +MINIO_SECRET_KEY= + +# Authentication +KEYCLOAK_URL=https://auth.harborsmith.com +KEYCLOAK_REALM=harborsmith +KEYCLOAK_CLIENT_ID=harborsmith-webapp + +# Payments +STRIPE_SECRET_KEY=sk_live_ +CAL_API_KEY=cal_live_ +``` + +## Critical Implementation Details + +### WebSocket Scaling +- nginx uses `ip_hash` for sticky sessions +- Socket.io configured with Redis adapter +- Connection state recovery enabled + +### Database Optimization +- PgBouncer for connection pooling (transaction mode) +- Proper indexes on all foreign keys +- PostgreSQL performance tuning applied + +### Security Configuration +- CORS with origin validation +- Application-level rate limiting +- JWT refresh token rotation +- All containers expose ports only to localhost + +### Caching Strategy +- Cache-aside pattern with Redis +- TTL strategies per data type +- Cache warming for popular content + +### Media Handling +- Uppy for resumable uploads (10GB+) +- Tus protocol implementation +- FFmpeg for HLS video transcoding +- MinIO for S3-compatible storage + +## ๐Ÿ”ฅ Harborsmith-Specific MCP Workflows + +### Charter Booking Flow Issues +```bash +1. Zen:debug("charter booking failing") +2. Serena:find_symbol("CharterService/createBooking") +3. Browser-tools:getNetworkErrors() +4. Context7:get-library-docs("/stripe/stripe-node", topic="payments") +5. Fix issue with Serena:replace_symbol_body +6. Playwright:test booking flow +7. Zen:codereview(review_type="security") +``` + +### Yacht Maintenance Scheduling +```bash +1. Zen:planner("implement maintenance scheduling") +2. Serena:search_for_pattern("maintenance.*schedule") +3. Context7:resolve-library-id("node-cron") +4. Implement with BullMQ queues +5. Zen:testgen for maintenance service +6. Browser-tools:performance audit +``` + +### Payment Integration +```bash +1. Context7:get-library-docs("/stripe/stripe-node", topic="webhooks") +2. Zen:secaudit(focus="payment-processing") +3. Serena:find_symbol("PaymentService") +4. Implement Stripe webhook handler +5. Zen:codereview(review_type="security", severity_filter="high") +6. Playwright:test payment flow with test cards +``` + +### Admin Portal Dashboard +```bash +1. Zen:consensus("dashboard visualization library") +2. Context7:resolve-library-id("tremor") +3. Serena:find_symbol("AdminDashboard") +4. Implement with Tremor components +5. Browser-tools:runAccessibilityAudit() +6. Zen:codereview(focus="performance") +``` + +## Development Workflow with MCP + +### Before ANY Code Change: +1. **Use Serena** to understand current implementation + - `find_symbol` to locate code + - `find_referencing_symbols` to check impact +2. **Use Zen** to plan approach + - `planner` for complex tasks + - `thinkdeep` for architecture decisions + +### During Implementation: +1. **Use Context7** for library documentation +2. **Use Serena** for code modifications +3. **Use Zen chat** for second opinions + +### After Changes: +1. **ALWAYS run** `getDiagnostics()` via IDE tools +2. **Use Zen** `codereview` for quality check +3. **Use Zen** `precommit` before committing + +### For Debugging: +1. **Start with Zen** `debug` for systematic investigation +2. **Use Browser-tools** for UI issues +3. **Use Serena** to navigate to problematic code + +## Monorepo Structure (Planned) + +``` +harborsmith/ +โ”œโ”€โ”€ apps/ +โ”‚ โ”œโ”€โ”€ website/ # Public SSG site +โ”‚ โ”œโ”€โ”€ webapp/ # Customer SPA +โ”‚ โ”œโ”€โ”€ portal/ # Admin portal +โ”‚ โ””โ”€โ”€ api/ # Main API +โ”œโ”€โ”€ packages/ +โ”‚ โ”œโ”€โ”€ shared/ # Shared types/utils +โ”‚ โ”œโ”€โ”€ ui/ # UI components +โ”‚ โ””โ”€โ”€ database/ # Prisma schema +โ””โ”€โ”€ services/ # Microservices + โ”œโ”€โ”€ charter/ + โ”œโ”€โ”€ maintenance/ + โ”œโ”€โ”€ payments/ + โ””โ”€โ”€ notifications/ +``` + +## ๐Ÿงช Automated Testing & Review Patterns + +### MANDATORY Test Automation for Every Feature +```bash +# 1. Generate comprehensive tests +Zen:testgen(target="CharterService", confidence="high") + +# 2. Run unit tests +npm run test:unit -- --coverage + +# 3. E2E automation +Playwright:browser_navigate("/charter/new") +Playwright:browser_fill_form([ + {name: "vessel", value: "Yacht001"}, + {name: "date", value: "2024-03-15"}, + {name: "duration", value: "4"} +]) +Playwright:browser_click("Book Charter") +Playwright:browser_wait_for(text="Booking Confirmed") + +# 4. Validate UI +Browser-tools:runAccessibilityAudit() +Browser-tools:runPerformanceAudit() +Browser-tools:getConsoleErrors() + +# 5. Security review +Zen:secaudit(audit_focus="owasp", threat_level="high") +``` + +### Code Review Automation +```bash +# After EVERY significant change +Zen:codereview( + relevant_files=["apps/api/src/**"], + review_type="full", + severity_filter="medium", + model="gemini-2.5-pro" +) + +# Security-focused review for auth/payment +Zen:codereview( + review_type="security", + focus_on="authentication,authorization,payment", + model="o3" +) + +# Performance review for critical paths +Zen:codereview( + review_type="performance", + focus_on="database queries,caching,api response time", + model="gemini-2.5-flash" +) +``` + +### Continuous Testing Pattern +```bash +# Watch mode during development +npm run test:watch + +# After each code change +IDE:getDiagnostics() +npm run lint +npm run typecheck + +# Before commit +Zen:precommit( + focus_on="security,completeness,tests", + severity_filter="high" +) + +# Full validation before deploy +./scripts/run-all-tests.sh +Zen:codereview(review_type="full") +Browser-tools:runAuditMode() +``` + +## MCP-First Development Principles + +1. **NEVER work without MCP tools** - Always gather context first +2. **Chain operations** - Each MCP output informs the next action +3. **Parallel when possible** - Run independent MCP calls simultaneously +4. **Verify constantly** - Use diagnostic tools after every change +5. **Think through Zen** - Complex reasoning must use Zen tools +6. **Document in memories** - Use Serena to persist important decisions + +## ๐Ÿš€ Harborsmith Feature Implementation Patterns + +### Crew Management Feature +```bash +# Planning +Zen:planner("crew management system with scheduling") +Zen:consensus([{model:"o3",stance:"for"},{model:"gemini-2.5-pro",stance:"neutral"}]) + +# Research +Context7:get-library-docs("/prisma/prisma", topic="relations") +Serena:search_for_pattern("crew|staff|employee") + +# Implementation +Serena:insert_after_symbol("YachtModel", "crew relation") +Serena:create CrewService with CRUD operations +Implement crew scheduling with BullMQ + +# Validation +Zen:testgen("CrewService", confidence="high") +Playwright:test crew assignment flow +Zen:codereview(review_type="full") +``` + +### Real-time Vessel Tracking +```bash +# Architecture +Zen:thinkdeep("real-time vessel tracking architecture") +Context7:get-library-docs("/socketio/socket.io", topic="namespaces") + +# Implementation +Serena:find_symbol("SocketService") +Implement vessel namespace in Socket.io +Add Redis adapter for scaling +Integrate with AIS data provider + +# Testing +Playwright:browser_navigate("/vessel-tracker") +Browser-tools:getNetworkLogs() # Check WebSocket +Zen:codereview(focus="real-time-performance") +``` + +### Weather Integration +```bash +# Research +Context7:resolve-library-id("openweather") +Zen:consensus("weather API provider selection") + +# Implementation +Serena:create WeatherService +Implement caching with Redis (1hr TTL) +Add weather alerts for charters + +# Validation +Zen:testgen("WeatherService") +Browser-tools:runPerformanceAudit() +Zen:precommit() +``` + +## Common MCP Workflows + +### Bug Investigation +``` +1. Zen debug (start investigation) +2. Browser-tools (gather symptoms) +3. Serena find_symbol (locate code) +4. Zen debug (refine hypothesis) +5. Serena replace_symbol_body (fix) +6. Zen codereview (verify fix) +``` + +### Feature Implementation +``` +1. Zen planner (break down task) +2. Context7 (get library docs) +3. Serena find_symbol (understand current code) +4. Implement changes +5. Zen codereview (quality check) +6. Playwright (test user flow) +``` + +### Performance Optimization +``` +1. Browser-tools audits (baseline) +2. Zen thinkdeep (analyze bottlenecks) +3. Serena search_for_pattern (find issues) +4. Optimize code +5. Browser-tools audits (verify improvement) +``` + +## IMPORTANT: External MinIO Integration + +MinIO runs in a SEPARATE Docker Compose stack. The application connects via: +- `MINIO_ENDPOINT`: External MinIO host/IP +- `MINIO_PORT`: Usually 9000 +- Ensure network connectivity between Docker containers and MinIO + +## ๐Ÿš€ Production Deployment & Monitoring Workflows + +### Pre-Deployment MCP Validation +```bash +# 1. Comprehensive security audit +Zen:secaudit( + audit_focus="comprehensive", + threat_level="high", + compliance_requirements=["PCI DSS", "GDPR"] +) + +# 2. Performance baseline +Browser-tools:runPerformanceAudit() +Browser-tools:runBestPracticesAudit() + +# 3. Final code review +Zen:codereview( + review_type="full", + review_validation_type="external", + model="o3" +) + +# 4. Pre-commit validation +Zen:precommit( + precommit_type="external", + severity_filter="medium", + include_staged=true, + include_unstaged=true +) +``` + +### Deployment Automation +```bash +# 1. Build and test +docker-compose build +docker-compose run --rm api npm test +docker-compose run --rm api npm run test:e2e + +# 2. Database migration +docker-compose run --rm api npx prisma migrate deploy + +# 3. Rolling deployment +./scripts/deploy.sh --strategy=rolling + +# 4. Health verification +./scripts/wait-for-healthy.sh + +# 5. Post-deployment validation +Playwright:browser_navigate("https://harborsmith.com") +Browser-tools:runAuditMode() +``` + +### Monitoring & Alerting Patterns +```bash +# Container health monitoring +docker-compose ps +docker-compose logs -f --tail=100 + +# Performance monitoring +Browser-tools:runPerformanceAudit() # Every hour +Zen:analyze(analysis_type="performance") # Daily + +# Error tracking +Browser-tools:getConsoleErrors() # Real-time +Zen:debug(confidence="exploring") # On error spike + +# Security monitoring +Zen:secaudit(audit_focus="dependencies") # Weekly +Browser-tools:runBestPracticesAudit() # Daily +``` + +### Incident Response Workflow +```bash +# 1. Immediate assessment +Zen:debug( + step="Production incident: [description]", + confidence="exploring", + model="o3" +) + +# 2. Gather evidence +Browser-tools:getConsoleErrors() +Browser-tools:getNetworkErrors() +docker-compose logs --tail=1000 api + +# 3. Root cause analysis +Zen:thinkdeep( + focus_areas=["performance", "security", "architecture"], + thinking_mode="high" +) + +# 4. Implement fix +Serena:find_symbol("[problematic code]") +Serena:replace_symbol_body() +Zen:codereview(review_type="quick") + +# 5. Deploy hotfix +./scripts/deploy-hotfix.sh +Zen:precommit(precommit_type="internal") +``` + +### Backup & Recovery +```bash +# Database backup +docker exec harborsmith_postgres pg_dump -U $DB_USER harborsmith > backup_$(date +%Y%m%d_%H%M%S).sql + +# MinIO data sync +rclone sync harborsmith-minio:/ backup/minio/ + +# Configuration backup +cp -r .env* docker-compose.yml nginx.conf backup/config/ + +# Recovery validation +docker-compose down +docker-compose up -d +./scripts/wait-for-healthy.sh +Playwright:test critical user flows +``` + +## Security Checklist + +Before ANY commit: +- [ ] Run Zen `precommit` validation +- [ ] No secrets in code +- [ ] CORS properly configured +- [ ] Rate limiting implemented +- [ ] Input validation with Zod +- [ ] SQL injection prevented (Prisma) + +## Performance Targets + +- Page load: < 1 second +- API response: < 200ms +- Database queries: < 50ms +- Upload support: 10GB+ files +- Concurrent users: 10,000+ + +## ๐Ÿ”ด MANDATORY MCP CHECKLIST + +**BEFORE responding to ANY request:** +- [ ] Did user mention a file/function? โ†’ `Serena:find_symbol()` NOW +- [ ] Is this about a library? โ†’ `Context7:docs` FIRST +- [ ] Any UI/browser mentioned? โ†’ `Browser-tools/Playwright` IMMEDIATELY +- [ ] Did I write/change code? โ†’ `IDE:getDiagnostics()` MANDATORY +- [ ] Is this complex? โ†’ `Zen:analysis` REQUIRED +- [ ] Multiple steps? โ†’ `Zen:planner` FIRST +- [ ] About to commit? โ†’ `Zen:precommit` CHECK + +## ๐ŸŽฏ Harborsmith Domain-Specific Triggers + +| Domain | Auto-Trigger MCP Chain | +|--------|------------------------| +| **Charter Operations** | Serena:find("CharterService") โ†’ Zen:analyze โ†’ Test booking flow | +| **Vessel Management** | Serena:find("Yacht*") โ†’ Check maintenance โ†’ Verify availability | +| **Payment Processing** | Context7:stripe docs โ†’ Zen:secaudit โ†’ Implement webhooks | +| **Crew Scheduling** | Zen:planner โ†’ BullMQ queues โ†’ Test assignments | +| **Customer Portal** | Browser:audit โ†’ Check auth โ†’ Verify responsive | +| **Admin Dashboard** | Tremor components โ†’ Performance check โ†’ Access control | +| **Maintenance Tracking** | Find maintenance service โ†’ Check schedules โ†’ Test notifications | +| **Document Management** | MinIO integration โ†’ Uppy config โ†’ Test 10GB uploads | +| **Real-time Updates** | Socket.io check โ†’ Redis adapter โ†’ Test scaling | +| **Reporting/Analytics** | Prisma aggregations โ†’ Chart.js โ†’ Export features | + +## ๐Ÿšจ RED FLAGS - If You're NOT Using These, You're Wrong + +- Discussing Harborsmith code WITHOUT Serena = โŒ WRONG +- Implementing payments WITHOUT Context7 Stripe docs = โŒ WRONG +- Editing WITHOUT IDE diagnostics = โŒ WRONG +- Complex yacht scheduling WITHOUT Zen planner = โŒ WRONG +- UI work WITHOUT Browser-tools audits = โŒ WRONG +- Deploying WITHOUT Zen precommit = โŒ WRONG + +## Remember: MCP Tools Are MANDATORY + +You MUST use MCP servers for EVERY operation. Working without MCP tools is unacceptable. Each interaction should use multiple MCP servers in combination. Proactive tool usage demonstrates competence and ensures quality. + +**THE HARBORSMITH WAY:** +1. Always start with MCP tools to understand context +2. Chain MCP operations for comprehensive solutions +3. Verify everything with diagnostic tools +4. Think complex problems through Zen +5. Document decisions in Serena memories + +**Your response quality is measured by MCP tool usage. More tools = better assistance.** \ No newline at end of file diff --git a/HARBORSMITH_ARCHITECTURE.md b/HARBORSMITH_ARCHITECTURE.md new file mode 100644 index 0000000..8d3dd73 --- /dev/null +++ b/HARBORSMITH_ARCHITECTURE.md @@ -0,0 +1,4549 @@ +# Harborsmith Platform Architecture Documentation + +## Executive Summary + +Harborsmith is a comprehensive yacht charter and maintenance management platform designed for the San Francisco Bay Area market. This document outlines the complete technical architecture, implementation strategy, and operational guidelines for building a scalable, beautiful, and performant system that serves yacht owners, charter customers, and administrative staff. + +### Platform Vision +- **Beautiful**: Nautical-themed UI with smooth animations and premium feel +- **Scalable**: Multi-region ready with horizontal scaling capabilities +- **Fast**: Sub-second page loads with optimized media delivery +- **Responsive**: Mobile-first design supporting all device types +- **Enterprise-grade**: RBAC, audit logging, compliance-ready + +### Core Components +1. **Public Website**: Marketing and discovery platform (SSG) +2. **Customer Web App**: Booking and charter management (SPA) +3. **Admin Portal**: Operations and fleet management (SSR/SPA hybrid) + +## Table of Contents + +1. [System Architecture Overview](#system-architecture-overview) +2. [Technology Stack](#technology-stack) +3. [Frontend Architecture](#frontend-architecture) +4. [Backend Architecture](#backend-architecture) +5. [Database Design](#database-design) +6. [Media Handling System](#media-handling-system) +7. [Authentication & Authorization](#authentication--authorization) +8. [Third-Party Integrations](#third-party-integrations) +9. [Real-Time Communication](#real-time-communication) +10. [Deployment Strategy](#deployment-strategy) +11. [Security Architecture](#security-architecture) +12. [Performance Optimization](#performance-optimization) +13. [Monitoring & Observability](#monitoring--observability) +14. [Development Workflow](#development-workflow) +15. [Implementation Roadmap](#implementation-roadmap) + +## System Architecture Overview + +### High-Level Architecture + +```mermaid +graph TB + subgraph "Client Layer" + PW[Public Website
Nuxt SSG] + CWA[Customer WebApp
Nuxt SPA] + AP[Admin Portal
Nuxt SSR/SPA] + end + + subgraph "API Gateway" + TR[Traefik
Load Balancer] + CACHE[Redis Cache] + end + + subgraph "Application Layer" + API[Fastify + tRPC API] + WS[Socket.io Server] + MEDIA[Media Service
Tus + FFmpeg] + end + + subgraph "Data Layer" + PG[(PostgreSQL
Primary DB)] + REDIS[(Redis
Cache & Sessions)] + MINIO[MinIO
Object Storage] + end + + subgraph "External Services" + KC[Keycloak
Identity] + CAL[Cal.com
Scheduling] + STRIPE[Stripe
Payments] + DIR[Directus
CMS] + end + + PW --> TR + CWA --> TR + AP --> TR + TR --> API + TR --> WS + TR --> MEDIA + API --> PG + API --> REDIS + API --> MINIO + API --> KC + API --> CAL + API --> STRIPE + API --> DIR +``` + +### Monorepo Structure + +``` +harborsmith/ +โ”œโ”€โ”€ apps/ +โ”‚ โ”œโ”€โ”€ website/ # Public marketing site (Nuxt SSG) +โ”‚ โ”œโ”€โ”€ webapp/ # Customer application (Nuxt SPA) +โ”‚ โ”œโ”€โ”€ portal/ # Admin portal (Nuxt SSR/SPA) +โ”‚ โ””โ”€โ”€ api/ # Backend API (Fastify + tRPC) +โ”œโ”€โ”€ packages/ +โ”‚ โ”œโ”€โ”€ shared/ # Shared types, utils, constants +โ”‚ โ”œโ”€โ”€ ui/ # Shared UI components library +โ”‚ โ”œโ”€โ”€ auth/ # Auth utilities and guards +โ”‚ โ”œโ”€โ”€ media/ # Media handling utilities +โ”‚ โ””โ”€โ”€ database/ # Prisma schema and migrations +โ”œโ”€โ”€ infrastructure/ +โ”‚ โ”œโ”€โ”€ docker/ # Docker configurations +โ”‚ โ”œโ”€โ”€ k8s/ # Kubernetes manifests (future) +โ”‚ โ””โ”€โ”€ terraform/ # Infrastructure as code (future) +โ”œโ”€โ”€ docs/ +โ”‚ โ”œโ”€โ”€ api/ # API documentation +โ”‚ โ”œโ”€โ”€ guides/ # Implementation guides +โ”‚ โ””โ”€โ”€ decisions/ # Architecture decision records +โ”œโ”€โ”€ tools/ +โ”‚ โ”œโ”€โ”€ scripts/ # Build and deployment scripts +โ”‚ โ””โ”€โ”€ generators/ # Code generators +โ”œโ”€โ”€ docker-compose.yml +โ”œโ”€โ”€ turbo.json # Turborepo configuration +โ”œโ”€โ”€ package.json +โ””โ”€โ”€ tsconfig.json +``` + +## Technology Stack + +### Frontend Stack + +| Layer | Technology | Purpose | Justification | +|-------|-----------|---------|---------------| +| **Framework** | Nuxt 3.15+ | Universal Vue framework | SSG/SSR/SPA flexibility, excellent DX | +| **UI Library** | Nuxt UI v3 | Component library | Built for Nuxt 3, fully typed, customizable | +| **CSS Framework** | Tailwind CSS v4 | Utility-first CSS | Fast development, consistent design | +| **State Management** | Pinia | Vue state management | Type-safe, devtools support | +| **Animations** | Motion.dev | Animation library | Smooth, performant animations | +| **Charts** | Tremor | Dashboard components | Beautiful analytics components | +| **Forms** | VeeValidate + Zod | Form validation | Type-safe validation | +| **Icons** | Iconify | Icon system | Massive icon library | +| **Utilities** | VueUse | Composition utilities | Essential Vue composables | +| **Media Upload** | Uppy | File upload | 10GB+ support, resumable | +| **Video Player** | hls.js | Video streaming | HLS adaptive streaming | + +### Backend Stack + +| Layer | Technology | Purpose | Justification | +|-------|-----------|---------|---------------| +| **Runtime** | Node.js 20+ | JavaScript runtime | LTS, performance improvements | +| **Framework** | Fastify | Web framework | High performance, plugin ecosystem | +| **API Layer** | tRPC | Type-safe APIs | End-to-end type safety | +| **ORM** | Prisma | Database toolkit | Type-safe queries, migrations | +| **Validation** | Zod | Schema validation | Runtime + compile-time safety | +| **Queue** | BullMQ | Job queue | Reliable background jobs | +| **WebSocket** | Socket.io | Real-time | Fallback support, rooms | +| **Cache** | Redis | Caching layer | Performance, sessions | +| **Storage** | MinIO | Object storage | S3-compatible, on-premise | +| **Media** | FFmpeg | Media processing | Video transcoding, HLS | +| **Upload** | Tus Server | Resumable uploads | Large file support | + +### Infrastructure Stack + +| Component | Technology | Purpose | Configuration | +|-----------|-----------|---------|---------------| +| **Container** | Docker | Containerization | Multi-stage builds | +| **Orchestration** | Docker Compose | Local development | Hot reload support | +| **Proxy** | Traefik | Reverse proxy | Auto SSL, load balancing | +| **Database** | PostgreSQL 16 | Primary database | JSONB, full-text search | +| **Cache** | Redis 7 | Caching & sessions | Persistence enabled | +| **Storage** | MinIO | Object storage | Multi-tenant buckets | +| **Identity** | Keycloak | Authentication | OIDC/OAuth2 | +| **Monitoring** | Glitchtip | Error tracking | Sentry-compatible | +| **Email** | Poste.io | Email server | SMTP/IMAP | + +## Frontend Architecture + +### Component Architecture + +#### Design System Foundation + +```typescript +// packages/ui/tokens/design-tokens.ts +export const designTokens = { + colors: { + // Nautical Theme + ocean: { + 50: '#f0f9ff', + 100: '#e0f2fe', + 200: '#bae6fd', + 300: '#7dd3fc', + 400: '#38bdf8', + 500: '#0ea5e9', // Primary + 600: '#0284c7', + 700: '#0369a1', + 800: '#075985', + 900: '#0c4a6e', + 950: '#083344', + }, + sail: { + 50: '#fefce8', + 100: '#fef9c3', + 200: '#fef088', + 300: '#fde047', + 400: '#facc15', + 500: '#eab308', // Accent + 600: '#ca8a04', + 700: '#a16207', + 800: '#854d0e', + 900: '#713f12', + }, + harbor: { + 50: '#f9fafb', + 100: '#f3f4f6', + 200: '#e5e7eb', + 300: '#d1d5db', + 400: '#9ca3af', + 500: '#6b7280', // Neutral + 600: '#4b5563', + 700: '#374151', + 800: '#1f2937', + 900: '#111827', + 950: '#030712', + } + }, + spacing: { + xs: '0.5rem', + sm: '0.75rem', + md: '1rem', + lg: '1.5rem', + xl: '2rem', + '2xl': '3rem', + '3xl': '4rem', + }, + typography: { + fonts: { + heading: 'Cal Sans, system-ui, sans-serif', + body: 'Inter, system-ui, sans-serif', + mono: 'JetBrains Mono, monospace', + }, + sizes: { + xs: '0.75rem', + sm: '0.875rem', + base: '1rem', + lg: '1.125rem', + xl: '1.25rem', + '2xl': '1.5rem', + '3xl': '1.875rem', + '4xl': '2.25rem', + '5xl': '3rem', + } + }, + animation: { + timing: { + instant: '100ms', + fast: '200ms', + normal: '300ms', + slow: '500ms', + slower: '700ms', + }, + easing: { + linear: 'linear', + in: 'cubic-bezier(0.4, 0, 1, 1)', + out: 'cubic-bezier(0, 0, 0.2, 1)', + inOut: 'cubic-bezier(0.4, 0, 0.2, 1)', + bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)', + } + }, + shadows: { + sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)', + md: '0 4px 6px -1px rgb(0 0 0 / 0.1)', + lg: '0 10px 15px -3px rgb(0 0 0 / 0.1)', + xl: '0 20px 25px -5px rgb(0 0 0 / 0.1)', + '2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)', + inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.06)', + } +} +``` + +#### Shared UI Components + +```vue + + + + +``` + +### Application Structure + +#### Public Website (SSG) + +```typescript +// apps/website/nuxt.config.ts +export default defineNuxtConfig({ + extends: ['@harborsmith/ui'], + nitro: { + prerender: { + crawlLinks: true, + routes: [ + '/', + '/fleet', + '/services', + '/about', + '/contact', + // Dynamic routes from API + '/yachts/**', + ] + } + }, + modules: [ + '@nuxt/ui', + '@nuxt/image', + '@nuxtjs/seo', + '@nuxtjs/fontaine', + '@nuxtjs/partytown', + '@vueuse/nuxt', + ], + ui: { + global: true, + icons: ['heroicons', 'mdi', 'carbon'], + }, + image: { + provider: 'ipx', + domains: ['minio.harborsmith.com'], + alias: { + minio: 'https://minio.harborsmith.com', + }, + screens: { + xs: 320, + sm: 640, + md: 768, + lg: 1024, + xl: 1280, + xxl: 1536, + '2xl': 1536, + }, + }, + seo: { + redirectToCanonicalSiteUrl: true, + }, + experimental: { + payloadExtraction: false, + renderJsonPayloads: true, + componentIslands: true, + }, +}) +``` + +#### Customer Web App (SPA) + +```typescript +// apps/webapp/nuxt.config.ts +export default defineNuxtConfig({ + extends: ['@harborsmith/ui'], + ssr: false, + modules: [ + '@nuxt/ui', + '@pinia/nuxt', + '@vueuse/nuxt', + '@nuxtjs/device', + 'nuxt-viewport', + ], + runtimeConfig: { + public: { + apiUrl: process.env.NUXT_PUBLIC_API_URL, + wsUrl: process.env.NUXT_PUBLIC_WS_URL, + keycloakUrl: process.env.NUXT_PUBLIC_KEYCLOAK_URL, + keycloakRealm: process.env.NUXT_PUBLIC_KEYCLOAK_REALM, + keycloakClientId: process.env.NUXT_PUBLIC_KEYCLOAK_CLIENT_ID, + } + }, + pinia: { + storesDirs: ['./stores/**'], + }, + build: { + transpile: ['trpc-nuxt'], + }, +}) +``` + +#### Admin Portal (SSR/SPA Hybrid) + +```typescript +// apps/portal/nuxt.config.ts +export default defineNuxtConfig({ + extends: ['@harborsmith/ui'], + nitro: { + prerender: { + routes: ['/login', '/dashboard'], + } + }, + modules: [ + '@nuxt/ui', + '@pinia/nuxt', + '@vueuse/nuxt', + 'nuxt-viewport', + '@nuxtjs/i18n', + ], + ssr: true, + experimental: { + viewTransition: true, + crossOriginPrefetch: true, + }, +}) +``` + +### State Management + +```typescript +// apps/webapp/stores/booking.ts +import { defineStore } from 'pinia' +import { useNuxtData } from '#app' + +export const useBookingStore = defineStore('booking', () => { + // State + const currentBooking = ref(null) + const selectedYacht = ref(null) + const selectedDates = ref(null) + const selectedExtras = ref([]) + const bookingStep = ref('yacht') + + // Computed + const totalPrice = computed(() => { + if (!selectedYacht.value || !selectedDates.value) return 0 + + const hours = calculateHours(selectedDates.value) + const basePrice = selectedYacht.value.hourlyRate * hours + const extrasPrice = selectedExtras.value.reduce((sum, extra) => { + return sum + extra.price * (extra.perHour ? hours : 1) + }, 0) + + return basePrice + extrasPrice + }) + + const canProceed = computed(() => { + switch (bookingStep.value) { + case 'yacht': + return !!selectedYacht.value + case 'dates': + return !!selectedDates.value + case 'extras': + return true // Extras are optional + case 'payment': + return totalPrice.value > 0 + default: + return false + } + }) + + // Actions + const selectYacht = async (yacht: Yacht) => { + selectedYacht.value = yacht + await checkAvailability(yacht.id) + } + + const selectDates = async (dates: DateRange) => { + selectedDates.value = dates + if (selectedYacht.value) { + await checkAvailability(selectedYacht.value.id, dates) + } + } + + const createBooking = async () => { + const { $api } = useNuxtApp() + + const booking = await $api.bookings.create({ + yachtId: selectedYacht.value!.id, + startDate: selectedDates.value!.start, + endDate: selectedDates.value!.end, + extras: selectedExtras.value.map(e => e.id), + }) + + currentBooking.value = booking + return booking + } + + const checkAvailability = async (yachtId: string, dates?: DateRange) => { + const { $api } = useNuxtApp() + + return await $api.yachts.checkAvailability({ + yachtId, + startDate: dates?.start, + endDate: dates?.end, + }) + } + + const reset = () => { + currentBooking.value = null + selectedYacht.value = null + selectedDates.value = null + selectedExtras.value = [] + bookingStep.value = 'yacht' + } + + return { + // State + currentBooking, + selectedYacht, + selectedDates, + selectedExtras, + bookingStep, + // Computed + totalPrice, + canProceed, + // Actions + selectYacht, + selectDates, + createBooking, + checkAvailability, + reset, + } +}) +``` + +## Backend Architecture + +### API Structure + +#### Fastify Server Setup + +```typescript +// apps/api/src/server.ts +import Fastify from 'fastify' +import cors from '@fastify/cors' +import helmet from '@fastify/helmet' +import rateLimit from '@fastify/rate-limit' +import compress from '@fastify/compress' +import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify' +import { createContext } from './trpc/context' +import { appRouter } from './trpc/router' +import { tusPlugin } from './plugins/tus' +import { socketPlugin } from './plugins/socket' +import { metricsPlugin } from './plugins/metrics' + +export async function createServer() { + const server = Fastify({ + logger: { + level: process.env.LOG_LEVEL || 'info', + transport: { + target: '@axiomhq/pino', + options: { + dataset: process.env.AXIOM_DATASET, + token: process.env.AXIOM_TOKEN, + } + } + }, + maxParamLength: 5000, + bodyLimit: 100 * 1024 * 1024, // 100MB for file uploads + }) + + // Core plugins + await server.register(helmet, { + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + styleSrc: ["'self'", "'unsafe-inline'"], + scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"], + imgSrc: ["'self'", 'data:', 'https:'], + connectSrc: ["'self'", 'wss:', 'https:'], + } + } + }) + + await server.register(cors, { + origin: process.env.ALLOWED_ORIGINS?.split(',') || true, + credentials: true, + }) + + await server.register(compress, { + global: true, + threshold: 1024, + encodings: ['gzip', 'deflate', 'br'], + }) + + await server.register(rateLimit, { + max: 100, + timeWindow: '1 minute', + cache: 10000, + allowList: ['127.0.0.1'], + redis: { + host: process.env.REDIS_HOST, + port: parseInt(process.env.REDIS_PORT || '6379'), + } + }) + + // Custom plugins + await server.register(tusPlugin, { prefix: '/upload' }) + await server.register(socketPlugin, { prefix: '/ws' }) + await server.register(metricsPlugin, { prefix: '/metrics' }) + + // tRPC + await server.register(fastifyTRPCPlugin, { + prefix: '/trpc', + trpcOptions: { + router: appRouter, + createContext, + onError({ path, error }) { + server.log.error({ path, error }, 'tRPC error') + }, + } + }) + + // Health check + server.get('/health', async (request, reply) => { + const checks = await performHealthChecks() + const healthy = Object.values(checks).every(check => check.status === 'healthy') + + reply.code(healthy ? 200 : 503).send({ + status: healthy ? 'healthy' : 'unhealthy', + timestamp: new Date().toISOString(), + checks, + }) + }) + + return server +} + +// Start server +const start = async () => { + const server = await createServer() + + try { + await server.listen({ + port: parseInt(process.env.PORT || '3000'), + host: '0.0.0.0', + }) + + server.log.info(`Server listening on ${server.server.address()}`) + } catch (err) { + server.log.error(err) + process.exit(1) + } +} + +start() +``` + +#### tRPC Router Architecture + +```typescript +// apps/api/src/trpc/router.ts +import { t } from './trpc' +import { authRouter } from './routers/auth' +import { yachtsRouter } from './routers/yachts' +import { bookingsRouter } from './routers/bookings' +import { usersRouter } from './routers/users' +import { paymentsRouter } from './routers/payments' +import { mediaRouter } from './routers/media' +import { maintenanceRouter } from './routers/maintenance' +import { analyticsRouter } from './routers/analytics' + +export const appRouter = t.router({ + auth: authRouter, + yachts: yachtsRouter, + bookings: bookingsRouter, + users: usersRouter, + payments: paymentsRouter, + media: mediaRouter, + maintenance: maintenanceRouter, + analytics: analyticsRouter, +}) + +export type AppRouter = typeof appRouter +``` + +```typescript +// apps/api/src/trpc/routers/yachts.ts +import { z } from 'zod' +import { t, protectedProcedure, adminProcedure } from '../trpc' +import { YachtService } from '../../services/yacht.service' +import { TRPCError } from '@trpc/server' + +const yachtInput = z.object({ + name: z.string().min(1).max(100), + model: z.string(), + year: z.number().min(1900).max(new Date().getFullYear() + 1), + length: z.number().min(10).max(500), + capacity: z.number().min(1).max(100), + cabins: z.number().min(0).max(20), + hourlyRate: z.number().min(0), + dailyRate: z.number().min(0), + features: z.array(z.string()), + description: z.string(), + location: z.object({ + marina: z.string(), + berth: z.string().optional(), + latitude: z.number(), + longitude: z.number(), + }), +}) + +export const yachtsRouter = t.router({ + // Public procedures + list: t.procedure + .input(z.object({ + page: z.number().min(1).default(1), + limit: z.number().min(1).max(100).default(20), + filters: z.object({ + location: z.string().optional(), + capacity: z.number().optional(), + priceRange: z.object({ + min: z.number().optional(), + max: z.number().optional(), + }).optional(), + features: z.array(z.string()).optional(), + available: z.object({ + from: z.date(), + to: z.date(), + }).optional(), + }).optional(), + sort: z.enum(['price', 'capacity', 'rating', 'popular']).default('popular'), + })) + .query(async ({ input, ctx }) => { + const service = new YachtService(ctx.prisma) + return service.listYachts(input) + }), + + getById: t.procedure + .input(z.string().uuid()) + .query(async ({ input, ctx }) => { + const service = new YachtService(ctx.prisma) + const yacht = await service.getYacht(input) + + if (!yacht) { + throw new TRPCError({ + code: 'NOT_FOUND', + message: 'Yacht not found', + }) + } + + return yacht + }), + + checkAvailability: t.procedure + .input(z.object({ + yachtId: z.string().uuid(), + startDate: z.date(), + endDate: z.date(), + })) + .query(async ({ input, ctx }) => { + const service = new YachtService(ctx.prisma) + return service.checkAvailability(input) + }), + + // Protected procedures (require auth) + create: adminProcedure + .input(yachtInput) + .mutation(async ({ input, ctx }) => { + const service = new YachtService(ctx.prisma) + return service.createYacht({ + ...input, + ownerId: ctx.user.id, + }) + }), + + update: adminProcedure + .input(z.object({ + id: z.string().uuid(), + data: yachtInput.partial(), + })) + .mutation(async ({ input, ctx }) => { + const service = new YachtService(ctx.prisma) + + // Check ownership + const yacht = await service.getYacht(input.id) + if (!yacht || (yacht.ownerId !== ctx.user.id && ctx.user.role !== 'ADMIN')) { + throw new TRPCError({ + code: 'FORBIDDEN', + message: 'You do not have permission to update this yacht', + }) + } + + return service.updateYacht(input.id, input.data) + }), + + delete: adminProcedure + .input(z.string().uuid()) + .mutation(async ({ input, ctx }) => { + const service = new YachtService(ctx.prisma) + + // Check ownership + const yacht = await service.getYacht(input) + if (!yacht || (yacht.ownerId !== ctx.user.id && ctx.user.role !== 'ADMIN')) { + throw new TRPCError({ + code: 'FORBIDDEN', + message: 'You do not have permission to delete this yacht', + }) + } + + return service.deleteYacht(input) + }), + + // Analytics + getStats: protectedProcedure + .input(z.string().uuid()) + .query(async ({ input, ctx }) => { + const service = new YachtService(ctx.prisma) + return service.getYachtStats(input, ctx.user.id) + }), +}) +``` + +### Service Layer Architecture + +```typescript +// apps/api/src/services/yacht.service.ts +import { PrismaClient, Prisma } from '@prisma/client' +import { cache } from '../lib/cache' +import { EventEmitter } from '../lib/events' + +export class YachtService { + constructor( + private prisma: PrismaClient, + private events: EventEmitter = new EventEmitter(), + ) {} + + async listYachts(params: ListYachtsParams) { + const cacheKey = `yachts:list:${JSON.stringify(params)}` + + // Check cache first + const cached = await cache.get(cacheKey) + if (cached) return cached + + const where: Prisma.YachtWhereInput = {} + + // Build filters + if (params.filters) { + if (params.filters.location) { + where.location = { + marina: { + contains: params.filters.location, + mode: 'insensitive', + } + } + } + + if (params.filters.capacity) { + where.capacity = { gte: params.filters.capacity } + } + + if (params.filters.priceRange) { + where.hourlyRate = { + gte: params.filters.priceRange.min, + lte: params.filters.priceRange.max, + } + } + + if (params.filters.features?.length) { + where.features = { + hasEvery: params.filters.features, + } + } + + if (params.filters.available) { + // Complex availability check + where.bookings = { + none: { + OR: [ + { + startDate: { + lte: params.filters.available.to, + }, + endDate: { + gte: params.filters.available.from, + }, + status: { + in: ['CONFIRMED', 'PENDING'], + } + } + ] + } + } + } + } + + // Build order by + const orderBy: Prisma.YachtOrderByWithRelationInput = {} + switch (params.sort) { + case 'price': + orderBy.hourlyRate = 'asc' + break + case 'capacity': + orderBy.capacity = 'desc' + break + case 'rating': + orderBy.rating = 'desc' + break + case 'popular': + default: + orderBy.bookingCount = 'desc' + break + } + + // Execute query with pagination + const [total, yachts] = await Promise.all([ + this.prisma.yacht.count({ where }), + this.prisma.yacht.findMany({ + where, + orderBy, + skip: (params.page - 1) * params.limit, + take: params.limit, + include: { + media: { + where: { isPrimary: true }, + take: 1, + }, + reviews: { + select: { + rating: true, + } + } + } + }) + ]) + + // Process results + const results = yachts.map(yacht => ({ + ...yacht, + primaryImage: yacht.media[0]?.url, + averageRating: yacht.reviews.length + ? yacht.reviews.reduce((sum, r) => sum + r.rating, 0) / yacht.reviews.length + : null, + })) + + const response = { + yachts: results, + pagination: { + page: params.page, + limit: params.limit, + total, + totalPages: Math.ceil(total / params.limit), + } + } + + // Cache for 5 minutes + await cache.set(cacheKey, response, 300) + + return response + } + + async createYacht(data: CreateYachtData) { + const yacht = await this.prisma.yacht.create({ + data: { + ...data, + slug: this.generateSlug(data.name), + status: 'DRAFT', + }, + include: { + owner: true, + } + }) + + // Emit event for other services + this.events.emit('yacht.created', { yacht }) + + // Invalidate cache + await cache.deletePattern('yachts:list:*') + + return yacht + } + + async updateYacht(id: string, data: UpdateYachtData) { + const yacht = await this.prisma.yacht.update({ + where: { id }, + data: { + ...data, + updatedAt: new Date(), + }, + }) + + // Emit event + this.events.emit('yacht.updated', { yacht }) + + // Invalidate cache + await Promise.all([ + cache.delete(`yacht:${id}`), + cache.deletePattern('yachts:list:*'), + ]) + + return yacht + } + + async checkAvailability({ yachtId, startDate, endDate }: CheckAvailabilityParams) { + const conflicts = await this.prisma.booking.findMany({ + where: { + yachtId, + status: { + in: ['CONFIRMED', 'PENDING'], + }, + OR: [ + { + startDate: { + lte: endDate, + }, + endDate: { + gte: startDate, + }, + } + ] + }, + select: { + startDate: true, + endDate: true, + status: true, + } + }) + + const maintenanceSchedules = await this.prisma.maintenanceSchedule.findMany({ + where: { + yachtId, + status: 'SCHEDULED', + startDate: { + lte: endDate, + }, + endDate: { + gte: startDate, + }, + }, + select: { + startDate: true, + endDate: true, + type: true, + } + }) + + return { + available: conflicts.length === 0 && maintenanceSchedules.length === 0, + conflicts: conflicts.map(c => ({ + start: c.startDate, + end: c.endDate, + type: 'booking' as const, + status: c.status, + })), + maintenance: maintenanceSchedules.map(m => ({ + start: m.startDate, + end: m.endDate, + type: 'maintenance' as const, + maintenanceType: m.type, + })), + } + } + + private generateSlug(name: string): string { + return name + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + } +} +``` + +## Database Design + +### Prisma Schema + +```prisma +// packages/database/prisma/schema.prisma +generator client { + provider = "prisma-client-js" + previewFeatures = ["fullTextSearch", "postgresqlExtensions"] +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + extensions = [pgcrypto, postgis, pg_trgm] +} + +// ==================== USER & AUTH ==================== +model User { + id String @id @default(uuid()) + keycloakId String @unique + email String @unique + firstName String + lastName String + phone String? + avatar String? + role UserRole @default(CUSTOMER) + status UserStatus @default(ACTIVE) + + // Profile + profile Profile? + preferences Json @default("{}") + + // Relations + ownedYachts Yacht[] @relation("YachtOwner") + bookings Booking[] + reviews Review[] + payments Payment[] + notifications Notification[] + activityLogs ActivityLog[] + + // Timestamps + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + lastLoginAt DateTime? + + @@index([email]) + @@index([keycloakId]) + @@index([role, status]) +} + +enum UserRole { + CUSTOMER + OWNER + CAPTAIN + CREW + ADMIN + SUPER_ADMIN +} + +enum UserStatus { + ACTIVE + SUSPENDED + DELETED +} + +model Profile { + id String @id @default(uuid()) + userId String @unique + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + // Preferences + language String @default("en") + timezone String @default("America/Los_Angeles") + currency String @default("USD") + + // Boating Experience + boatingLicense String? + experienceLevel ExperienceLevel @default(BEGINNER) + certifications String[] + + // Emergency Contact + emergencyName String? + emergencyPhone String? + emergencyRelation String? + + // Documents + documents Document[] + + // Metadata + metadata Json @default("{}") + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +enum ExperienceLevel { + BEGINNER + INTERMEDIATE + ADVANCED + PROFESSIONAL +} + +// ==================== YACHT ==================== +model Yacht { + id String @id @default(uuid()) + slug String @unique + name String + model String + manufacturer String + year Int + + // Specifications + length Float // in feet + beam Float? // width in feet + draft Float? // depth in feet + capacity Int // number of guests + cabins Int + bathrooms Int + engineType String? + enginePower String? // horsepower + fuelCapacity Float? // in gallons + waterCapacity Float? // in gallons + + // Pricing + hourlyRate Float + halfDayRate Float? // 4 hours + fullDayRate Float? // 8 hours + weeklyRate Float? + securityDeposit Float + + // Location + location Json // { marina, berth, latitude, longitude } + homePort String + cruisingArea String[] + + // Features & Amenities + features String[] + amenities String[] + waterToys String[] + safetyEquipment String[] + navigationEquip String[] + + // Description + description String @db.Text + highlights String[] + rules String[] + + // Status + status YachtStatus @default(DRAFT) + available Boolean @default(true) + instantBooking Boolean @default(false) + + // Relations + ownerId String + owner User @relation("YachtOwner", fields: [ownerId], references: [id]) + captain Captain? + crew Crew[] + media Media[] + bookings Booking[] + reviews Review[] + maintenance MaintenanceSchedule[] + documents Document[] + insurance Insurance[] + + // Analytics + viewCount Int @default(0) + bookingCount Int @default(0) + rating Float? + + // Timestamps + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + lastServiceDate DateTime? + + @@index([slug]) + @@index([status, available]) + @@index([ownerId]) + @@index([hourlyRate]) + @@index([capacity]) + @@fulltext([name, model, manufacturer, description]) +} + +enum YachtStatus { + DRAFT + PENDING_REVIEW + ACTIVE + INACTIVE + MAINTENANCE + ARCHIVED +} + +// ==================== BOOKING ==================== +model Booking { + id String @id @default(uuid()) + bookingNumber String @unique + + // Relations + yachtId String + yacht Yacht @relation(fields: [yachtId], references: [id]) + userId String + user User @relation(fields: [userId], references: [id]) + + // Dates + startDate DateTime + endDate DateTime + duration Float // in hours + + // Guests + guestCount Int + guestDetails Json[] // Array of guest information + + // Pricing + basePrice Float + extrasPrice Float @default(0) + discountAmount Float @default(0) + taxAmount Float + totalPrice Float + depositAmount Float + + // Status + status BookingStatus @default(PENDING) + paymentStatus PaymentStatus @default(PENDING) + + // Extras + extras BookingExtra[] + + // Captain & Crew + captainRequired Boolean @default(true) + crewRequired Boolean @default(false) + assignedCaptain String? + assignedCrew String[] + + // Check-in/out + checkInTime DateTime? + checkOutTime DateTime? + checkInNotes String? + checkOutNotes String? + damageReport Json? + + // Payment + payments Payment[] + refunds Refund[] + + // Communication + messages Message[] + + // Metadata + source String @default("WEBSITE") // WEBSITE, APP, PHONE, PARTNER + specialRequests String? + internalNotes String? + cancellationReason String? + + // Timestamps + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + confirmedAt DateTime? + cancelledAt DateTime? + + @@index([bookingNumber]) + @@index([yachtId, startDate, endDate]) + @@index([userId]) + @@index([status, paymentStatus]) + @@index([startDate]) +} + +enum BookingStatus { + PENDING + CONFIRMED + IN_PROGRESS + COMPLETED + CANCELLED + NO_SHOW +} + +enum PaymentStatus { + PENDING + PARTIAL + PAID + REFUNDED + FAILED +} + +model BookingExtra { + id String @id @default(uuid()) + bookingId String + booking Booking @relation(fields: [bookingId], references: [id], onDelete: Cascade) + + name String + description String? + category String // CATERING, EQUIPMENT, SERVICE, OTHER + quantity Int @default(1) + unitPrice Float + totalPrice Float + + createdAt DateTime @default(now()) + + @@index([bookingId]) +} + +// ==================== PAYMENT ==================== +model Payment { + id String @id @default(uuid()) + paymentNumber String @unique + + // Relations + bookingId String + booking Booking @relation(fields: [bookingId], references: [id]) + userId String + user User @relation(fields: [userId], references: [id]) + + // Amount + amount Float + currency String @default("USD") + + // Stripe + stripePaymentId String? @unique + stripePaymentIntent String? + stripeCustomerId String? + paymentMethod String // CARD, BANK, WALLET + + // Status + status PaymentTransactionStatus @default(PENDING) + + // Metadata + description String? + metadata Json @default("{}") + failureReason String? + + // Timestamps + createdAt DateTime @default(now()) + processedAt DateTime? + + @@index([paymentNumber]) + @@index([bookingId]) + @@index([userId]) + @@index([status]) + @@index([stripePaymentId]) +} + +enum PaymentTransactionStatus { + PENDING + PROCESSING + SUCCEEDED + FAILED + CANCELLED +} + +// ==================== MEDIA ==================== +model Media { + id String @id @default(uuid()) + + // Relations + yachtId String? + yacht Yacht? @relation(fields: [yachtId], references: [id], onDelete: Cascade) + reviewId String? + review Review? @relation(fields: [reviewId], references: [id], onDelete: Cascade) + + // File Info + fileName String + mimeType String + size Int // in bytes + + // URLs + url String // Public URL + thumbnailUrl String? // Thumbnail for images/videos + streamUrl String? // HLS stream URL for videos + + // MinIO + bucket String + objectKey String + etag String? + + // Type & Purpose + type MediaType + category String? // EXTERIOR, INTERIOR, DECK, CABIN, etc. + isPrimary Boolean @default(false) + order Int @default(0) + + // Video Specific + duration Float? // in seconds + resolution String? // 1080p, 4K, etc. + frameRate Float? + bitrate Int? + + // Processing + processingStatus ProcessingStatus @default(PENDING) + processingError String? + processedAt DateTime? + + // Metadata + alt String? + caption String? + metadata Json @default("{}") + + // Timestamps + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([yachtId, type, isPrimary]) + @@index([reviewId]) + @@index([processingStatus]) +} + +enum MediaType { + IMAGE + VIDEO + DOCUMENT + VIRTUAL_TOUR +} + +enum ProcessingStatus { + PENDING + PROCESSING + COMPLETED + FAILED +} + +// ==================== REVIEW ==================== +model Review { + id String @id @default(uuid()) + + // Relations + bookingId String @unique + booking Booking @relation(fields: [bookingId], references: [id]) + yachtId String + yacht Yacht @relation(fields: [yachtId], references: [id]) + userId String + user User @relation(fields: [userId], references: [id]) + + // Ratings (1-5 stars) + overallRating Float + cleanlinessRating Float? + accuracyRating Float? + valueRating Float? + serviceRating Float? + locationRating Float? + + // Content + title String? + content String @db.Text + pros String[] + cons String[] + + // Media + media Media[] + + // Response + ownerResponse String? @db.Text + ownerRespondedAt DateTime? + + // Status + status ReviewStatus @default(PENDING) + isVerified Boolean @default(false) + isFeatured Boolean @default(false) + + // Helpful votes + helpfulCount Int @default(0) + + // Timestamps + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + publishedAt DateTime? + + @@index([yachtId, status]) + @@index([userId]) + @@index([bookingId]) + @@index([overallRating]) +} + +enum ReviewStatus { + PENDING + APPROVED + REJECTED + HIDDEN +} + +// ==================== MAINTENANCE ==================== +model MaintenanceSchedule { + id String @id @default(uuid()) + + // Relations + yachtId String + yacht Yacht @relation(fields: [yachtId], references: [id]) + + // Schedule + type MaintenanceType + title String + description String? + startDate DateTime + endDate DateTime + + // Service Provider + provider String? + providerContact String? + estimatedCost Float? + actualCost Float? + + // Status + status MaintenanceStatus @default(SCHEDULED) + + // Notes + notes String? @db.Text + completionNotes String? @db.Text + + // Timestamps + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + completedAt DateTime? + + @@index([yachtId, startDate, endDate]) + @@index([status]) +} + +enum MaintenanceType { + ROUTINE + REPAIR + INSPECTION + CLEANING + UPGRADE + EMERGENCY +} + +enum MaintenanceStatus { + SCHEDULED + IN_PROGRESS + COMPLETED + CANCELLED +} + +// ==================== CAPTAIN & CREW ==================== +model Captain { + id String @id @default(uuid()) + yachtId String @unique + yacht Yacht @relation(fields: [yachtId], references: [id]) + + // Personal Info + firstName String + lastName String + email String + phone String + + // Credentials + licenseNumber String + licenseExpiry DateTime + certifications String[] + yearsExperience Int + + // Availability + availability Json // Calendar availability + hourlyRate Float + + // Status + status CrewStatus @default(ACTIVE) + + // Timestamps + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Crew { + id String @id @default(uuid()) + yachtId String + yacht Yacht @relation(fields: [yachtId], references: [id]) + + // Personal Info + firstName String + lastName String + role String // DECKHAND, CHEF, STEWARD, etc. + + // Contact + email String? + phone String? + + // Employment + hourlyRate Float? + status CrewStatus @default(ACTIVE) + + // Timestamps + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([yachtId]) +} + +enum CrewStatus { + ACTIVE + INACTIVE + ON_LEAVE + TERMINATED +} + +// ==================== COMMUNICATION ==================== +model Message { + id String @id @default(uuid()) + + // Relations + bookingId String + booking Booking @relation(fields: [bookingId], references: [id]) + senderId String + sender User @relation(fields: [senderId], references: [id]) + + // Content + content String @db.Text + attachments String[] + + // Status + isRead Boolean @default(false) + readAt DateTime? + + // Timestamps + createdAt DateTime @default(now()) + editedAt DateTime? + + @@index([bookingId]) + @@index([senderId]) +} + +model Notification { + id String @id @default(uuid()) + + // Relations + userId String + user User @relation(fields: [userId], references: [id]) + + // Content + type NotificationType + title String + message String + data Json @default("{}") + + // Status + isRead Boolean @default(false) + readAt DateTime? + + // Delivery + channels String[] // EMAIL, SMS, PUSH, IN_APP + emailSent Boolean @default(false) + smsSent Boolean @default(false) + pushSent Boolean @default(false) + + // Timestamps + createdAt DateTime @default(now()) + expiresAt DateTime? + + @@index([userId, isRead]) + @@index([type]) +} + +enum NotificationType { + BOOKING_CONFIRMED + BOOKING_CANCELLED + BOOKING_REMINDER + PAYMENT_SUCCESS + PAYMENT_FAILED + REVIEW_REQUEST + MAINTENANCE_SCHEDULED + SYSTEM_ANNOUNCEMENT + CUSTOM +} + +// ==================== DOCUMENTS ==================== +model Document { + id String @id @default(uuid()) + + // Relations (polymorphic) + entityType String // USER, YACHT, BOOKING, etc. + entityId String + + // Document Info + type DocumentType + name String + description String? + + // File + fileUrl String + fileName String + mimeType String + size Int + + // Status + status DocumentStatus @default(PENDING) + verifiedBy String? + verifiedAt DateTime? + expiresAt DateTime? + + // Timestamps + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([entityType, entityId]) + @@index([type, status]) +} + +enum DocumentType { + LICENSE + INSURANCE + REGISTRATION + INSPECTION + CONTRACT + WAIVER + ID_PROOF + OTHER +} + +enum DocumentStatus { + PENDING + VERIFIED + REJECTED + EXPIRED +} + +// ==================== AUDIT & ANALYTICS ==================== +model ActivityLog { + id String @id @default(uuid()) + + // Actor + userId String? + user User? @relation(fields: [userId], references: [id]) + + // Action + action String // CREATE, UPDATE, DELETE, VIEW, etc. + entityType String // YACHT, BOOKING, USER, etc. + entityId String + + // Details + changes Json? // Before/after values + metadata Json @default("{}") + + // Request Info + ipAddress String? + userAgent String? + + // Timestamp + createdAt DateTime @default(now()) + + @@index([userId]) + @@index([entityType, entityId]) + @@index([action]) + @@index([createdAt]) +} +``` + +## Media Handling System + +### Upload Architecture + +```typescript +// apps/api/src/plugins/tus.ts +import { FastifyPluginAsync } from 'fastify' +import { Server, EVENTS } from '@tus/server' +import { S3Store } from '@tus/s3-store' +import { Client } from 'minio' +import { nanoid } from 'nanoid' + +export const tusPlugin: FastifyPluginAsync = async (fastify) => { + const minioClient = new Client({ + endPoint: process.env.MINIO_ENDPOINT!, + port: parseInt(process.env.MINIO_PORT || '9000'), + useSSL: process.env.MINIO_USE_SSL === 'true', + accessKey: process.env.MINIO_ACCESS_KEY!, + secretKey: process.env.MINIO_SECRET_KEY!, + }) + + const tusServer = new Server({ + path: '/upload', + maxSize: 10 * 1024 * 1024 * 1024, // 10GB + datastore: new S3Store({ + s3Client: minioClient, + bucket: 'uploads', + partSize: 10 * 1024 * 1024, // 10MB parts + }), + namingFunction(req) { + const ext = req.headers['metadata']?.split('filename ')[1]?.split('.').pop() + return `${nanoid()}.${ext || 'bin'}` + }, + onUploadCreate: async (req, res, upload) => { + const metadata = upload.metadata + + // Validate user permissions + const token = req.headers.authorization?.replace('Bearer ', '') + if (!token) throw new Error('Unauthorized') + + const user = await validateToken(token) + if (!user) throw new Error('Invalid token') + + // Store upload record + await fastify.prisma.upload.create({ + data: { + id: upload.id, + userId: user.id, + fileName: metadata.filename, + mimeType: metadata.filetype, + size: upload.size, + status: 'UPLOADING', + } + }) + + return res + }, + onUploadFinish: async (req, res, upload) => { + const uploadRecord = await fastify.prisma.upload.findUnique({ + where: { id: upload.id } + }) + + if (!uploadRecord) throw new Error('Upload not found') + + // Process based on file type + const processor = getProcessor(uploadRecord.mimeType) + + if (processor) { + // Queue processing job + await fastify.queue.add('media.process', { + uploadId: upload.id, + type: processor, + source: `uploads/${upload.id}`, + }) + } + + // Update status + await fastify.prisma.upload.update({ + where: { id: upload.id }, + data: { + status: 'COMPLETED', + completedAt: new Date(), + } + }) + + // Emit event + fastify.events.emit('upload.completed', { upload }) + + return res + } + }) + + // Handle tus protocol + fastify.all('/upload', (req, reply) => { + tusServer.handle(req.raw, reply.raw) + }) + + fastify.all('/upload/*', (req, reply) => { + tusServer.handle(req.raw, reply.raw) + }) +} +``` + +### Video Processing Pipeline + +```typescript +// apps/api/src/workers/media.processor.ts +import { Worker } from 'bullmq' +import ffmpeg from 'fluent-ffmpeg' +import { Client } from 'minio' +import path from 'path' +import fs from 'fs/promises' + +const minioClient = new Client({ + endPoint: process.env.MINIO_ENDPOINT!, + port: parseInt(process.env.MINIO_PORT || '9000'), + useSSL: process.env.MINIO_USE_SSL === 'true', + accessKey: process.env.MINIO_ACCESS_KEY!, + secretKey: process.env.MINIO_SECRET_KEY!, +}) + +export const mediaProcessor = new Worker('media.process', async (job) => { + const { uploadId, type, source } = job.data + + switch (type) { + case 'video': + return processVideo(uploadId, source) + case 'image': + return processImage(uploadId, source) + default: + throw new Error(`Unknown processor type: ${type}`) + } +}, { + connection: { + host: process.env.REDIS_HOST, + port: parseInt(process.env.REDIS_PORT || '6379'), + }, + concurrency: 3, +}) + +async function processVideo(uploadId: string, source: string) { + const tempDir = `/tmp/video-${uploadId}` + await fs.mkdir(tempDir, { recursive: true }) + + try { + // Download source video + const sourceFile = path.join(tempDir, 'source.mp4') + await minioClient.fGetObject('uploads', source, sourceFile) + + // Get video info + const metadata = await getVideoMetadata(sourceFile) + + // Generate HLS streams + const hlsDir = path.join(tempDir, 'hls') + await fs.mkdir(hlsDir, { recursive: true }) + + // Create multiple quality variants + const variants = [ + { name: '1080p', width: 1920, height: 1080, bitrate: '5000k' }, + { name: '720p', width: 1280, height: 720, bitrate: '3000k' }, + { name: '480p', width: 854, height: 480, bitrate: '1500k' }, + { name: '360p', width: 640, height: 360, bitrate: '800k' }, + ] + + const playlists = [] + + for (const variant of variants) { + if (metadata.width >= variant.width) { + const outputDir = path.join(hlsDir, variant.name) + await fs.mkdir(outputDir, { recursive: true }) + + await new Promise((resolve, reject) => { + ffmpeg(sourceFile) + .outputOptions([ + `-vf scale=${variant.width}:${variant.height}`, + `-c:v libx264`, + `-b:v ${variant.bitrate}`, + `-c:a aac`, + `-b:a 128k`, + `-hls_time 6`, + `-hls_playlist_type vod`, + `-hls_segment_filename ${outputDir}/segment_%03d.ts`, + `-master_pl_name master.m3u8`, + ]) + .output(`${outputDir}/index.m3u8`) + .on('end', resolve) + .on('error', reject) + .run() + }) + + playlists.push({ + resolution: variant.name, + path: `${variant.name}/index.m3u8`, + }) + } + } + + // Create master playlist + const masterPlaylist = createMasterPlaylist(playlists) + await fs.writeFile(path.join(hlsDir, 'master.m3u8'), masterPlaylist) + + // Upload all HLS files to MinIO + const hlsFiles = await getFilesRecursive(hlsDir) + + for (const file of hlsFiles) { + const relativePath = path.relative(hlsDir, file) + const objectName = `videos/${uploadId}/hls/${relativePath}` + + await minioClient.fPutObject('media', objectName, file, { + 'Content-Type': file.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/MP2T', + 'Cache-Control': 'public, max-age=31536000', + }) + } + + // Generate thumbnail + const thumbnailPath = path.join(tempDir, 'thumbnail.jpg') + await new Promise((resolve, reject) => { + ffmpeg(sourceFile) + .screenshots({ + timestamps: ['10%'], + filename: 'thumbnail.jpg', + folder: tempDir, + size: '640x360', + }) + .on('end', resolve) + .on('error', reject) + }) + + await minioClient.fPutObject('media', `videos/${uploadId}/thumbnail.jpg`, thumbnailPath, { + 'Content-Type': 'image/jpeg', + 'Cache-Control': 'public, max-age=31536000', + }) + + // Update database + await prisma.media.update({ + where: { uploadId }, + data: { + processingStatus: 'COMPLETED', + processedAt: new Date(), + streamUrl: `https://cdn.harborsmith.com/videos/${uploadId}/hls/master.m3u8`, + thumbnailUrl: `https://cdn.harborsmith.com/videos/${uploadId}/thumbnail.jpg`, + duration: metadata.duration, + resolution: `${metadata.width}x${metadata.height}`, + frameRate: metadata.fps, + bitrate: metadata.bitrate, + } + }) + + } finally { + // Cleanup temp files + await fs.rm(tempDir, { recursive: true, force: true }) + } +} + +function createMasterPlaylist(playlists: any[]) { + let content = '#EXTM3U\n#EXT-X-VERSION:3\n' + + const bandwidthMap = { + '1080p': 5500000, + '720p': 3500000, + '480p': 1750000, + '360p': 900000, + } + + for (const playlist of playlists) { + const bandwidth = bandwidthMap[playlist.resolution] + content += `#EXT-X-STREAM-INF:BANDWIDTH=${bandwidth},RESOLUTION=${playlist.resolution}\n` + content += `${playlist.path}\n` + } + + return content +} +``` + +## Authentication & Authorization + +### Keycloak Integration + +```typescript +// packages/auth/src/keycloak.ts +import { FastifyPluginAsync } from 'fastify' +import fastifyJwt from '@fastify/jwt' +import axios from 'axios' + +interface KeycloakConfig { + realm: string + serverUrl: string + clientId: string + clientSecret: string +} + +export const keycloakPlugin: FastifyPluginAsync = async (fastify, options) => { + // JWT verification + await fastify.register(fastifyJwt, { + secret: { + public: await getKeycloakPublicKey(options), + }, + verify: { + algorithms: ['RS256'], + issuer: `${options.serverUrl}/realms/${options.realm}`, + audience: options.clientId, + } + }) + + // Add decorators + fastify.decorate('authenticate', async (request, reply) => { + try { + await request.jwtVerify() + + // Enrich with user data + const user = await getUserFromToken(request.user) + request.user = user + } catch (err) { + reply.code(401).send({ error: 'Unauthorized' }) + } + }) + + fastify.decorate('authorize', (roles: string[]) => { + return async (request, reply) => { + if (!request.user) { + return reply.code(401).send({ error: 'Unauthorized' }) + } + + const hasRole = roles.some(role => + request.user.roles?.includes(role) + ) + + if (!hasRole) { + return reply.code(403).send({ error: 'Forbidden' }) + } + } + }) +} + +async function getKeycloakPublicKey(config: KeycloakConfig) { + const response = await axios.get( + `${config.serverUrl}/realms/${config.realm}/protocol/openid-connect/certs` + ) + + const key = response.data.keys[0] + return `-----BEGIN PUBLIC KEY-----\n${key.x5c[0]}\n-----END PUBLIC KEY-----` +} +``` + +### RBAC Implementation + +```typescript +// apps/api/src/middleware/rbac.ts +import { TRPCError } from '@trpc/server' + +export const permissions = { + // Yacht permissions + 'yacht:create': ['ADMIN', 'OWNER'], + 'yacht:read': ['ADMIN', 'OWNER', 'CAPTAIN', 'CREW', 'CUSTOMER'], + 'yacht:update': ['ADMIN', 'OWNER'], + 'yacht:delete': ['ADMIN', 'OWNER'], + + // Booking permissions + 'booking:create': ['ADMIN', 'CUSTOMER'], + 'booking:read': ['ADMIN', 'OWNER', 'CAPTAIN', 'CUSTOMER'], + 'booking:update': ['ADMIN', 'OWNER'], + 'booking:cancel': ['ADMIN', 'OWNER', 'CUSTOMER'], + + // User permissions + 'user:read': ['ADMIN', 'SELF'], + 'user:update': ['ADMIN', 'SELF'], + 'user:delete': ['ADMIN'], + + // Admin permissions + 'admin:dashboard': ['ADMIN', 'SUPER_ADMIN'], + 'admin:users': ['ADMIN', 'SUPER_ADMIN'], + 'admin:reports': ['ADMIN', 'SUPER_ADMIN', 'OWNER'], +} + +export function checkPermission( + user: User, + permission: keyof typeof permissions, + context?: { ownerId?: string; userId?: string } +) { + const allowedRoles = permissions[permission] + + // Check if user has required role + if (allowedRoles.includes(user.role)) { + return true + } + + // Check for SELF permission + if (allowedRoles.includes('SELF') && context?.userId === user.id) { + return true + } + + // Check for ownership + if (user.role === 'OWNER' && context?.ownerId === user.id) { + return true + } + + throw new TRPCError({ + code: 'FORBIDDEN', + message: `Missing permission: ${permission}`, + }) +} +``` + +## Third-Party Integrations + +### Stripe Integration + +```typescript +// apps/api/src/services/stripe.service.ts +import Stripe from 'stripe' + +export class StripeService { + private stripe: Stripe + + constructor() { + this.stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { + apiVersion: '2023-10-16', + }) + } + + async createCustomer(user: User) { + const customer = await this.stripe.customers.create({ + email: user.email, + name: `${user.firstName} ${user.lastName}`, + metadata: { + userId: user.id, + } + }) + + // Save to database + await prisma.user.update({ + where: { id: user.id }, + data: { stripeCustomerId: customer.id } + }) + + return customer + } + + async createPaymentIntent(booking: Booking) { + // Calculate platform fee (10%) + const platformFee = Math.round(booking.totalPrice * 0.10 * 100) + + const paymentIntent = await this.stripe.paymentIntents.create({ + amount: Math.round(booking.totalPrice * 100), // Convert to cents + currency: 'usd', + customer: booking.user.stripeCustomerId, + description: `Booking ${booking.bookingNumber}`, + metadata: { + bookingId: booking.id, + yachtId: booking.yachtId, + userId: booking.userId, + }, + application_fee_amount: platformFee, + transfer_data: { + destination: booking.yacht.owner.stripeAccountId, + }, + capture_method: 'manual', // Hold funds until check-in + }) + + return paymentIntent + } + + async capturePayment(paymentIntentId: string) { + return await this.stripe.paymentIntents.capture(paymentIntentId) + } + + async refundPayment(paymentIntentId: string, amount?: number) { + return await this.stripe.refunds.create({ + payment_intent: paymentIntentId, + amount: amount ? Math.round(amount * 100) : undefined, + reason: 'requested_by_customer', + }) + } + + async createConnectedAccount(owner: User) { + const account = await this.stripe.accounts.create({ + type: 'express', + country: 'US', + email: owner.email, + capabilities: { + card_payments: { requested: true }, + transfers: { requested: true }, + }, + business_type: 'individual', + metadata: { + ownerId: owner.id, + } + }) + + // Create account link for onboarding + const accountLink = await this.stripe.accountLinks.create({ + account: account.id, + refresh_url: `${process.env.APP_URL}/portal/stripe/refresh`, + return_url: `${process.env.APP_URL}/portal/stripe/complete`, + type: 'account_onboarding', + }) + + return { account, accountLink } + } + + async handleWebhook(signature: string, payload: string) { + const event = this.stripe.webhooks.constructEvent( + payload, + signature, + process.env.STRIPE_WEBHOOK_SECRET! + ) + + switch (event.type) { + case 'payment_intent.succeeded': + await this.handlePaymentSuccess(event.data.object) + break + case 'payment_intent.payment_failed': + await this.handlePaymentFailure(event.data.object) + break + case 'account.updated': + await this.handleAccountUpdate(event.data.object) + break + // ... more event handlers + } + } +} +``` + +### Cal.com Integration + +```typescript +// apps/api/src/services/cal.service.ts +import axios from 'axios' + +export class CalService { + private apiKey: string + private baseUrl: string + + constructor() { + this.apiKey = process.env.CAL_API_KEY! + this.baseUrl = 'https://api.cal.com/v1' + } + + async createEventType(yacht: Yacht) { + const response = await axios.post( + `${this.baseUrl}/event-types`, + { + title: `${yacht.name} Charter`, + slug: yacht.slug, + description: yacht.description, + length: 60, // Default 1 hour slots + locations: [ + { + type: 'inPerson', + address: yacht.location.marina, + } + ], + metadata: { + yachtId: yacht.id, + }, + price: yacht.hourlyRate, + currency: 'USD', + }, + { + headers: { + 'Authorization': `Bearer ${this.apiKey}`, + } + } + ) + + return response.data + } + + async createBooking(booking: Booking) { + const response = await axios.post( + `${this.baseUrl}/bookings`, + { + eventTypeId: booking.yacht.calEventTypeId, + start: booking.startDate.toISOString(), + end: booking.endDate.toISOString(), + name: `${booking.user.firstName} ${booking.user.lastName}`, + email: booking.user.email, + phone: booking.user.phone, + guests: booking.guestDetails.map(g => g.email), + notes: booking.specialRequests, + metadata: { + bookingId: booking.id, + } + }, + { + headers: { + 'Authorization': `Bearer ${this.apiKey}`, + } + } + ) + + return response.data + } + + async cancelBooking(calBookingId: string, reason: string) { + const response = await axios.delete( + `${this.baseUrl}/bookings/${calBookingId}`, + { + data: { cancellationReason: reason }, + headers: { + 'Authorization': `Bearer ${this.apiKey}`, + } + } + ) + + return response.data + } + + async handleWebhook(payload: any) { + switch (payload.triggerEvent) { + case 'BOOKING_CREATED': + // Sync with our database + break + case 'BOOKING_CANCELLED': + // Update booking status + break + case 'BOOKING_RESCHEDULED': + // Update dates + break + } + } +} +``` + +## Real-Time Communication + +### Socket.io Implementation + +```typescript +// apps/api/src/plugins/socket.ts +import { Server } from 'socket.io' +import { createAdapter } from '@socket.io/redis-adapter' +import { Redis } from 'ioredis' + +export const socketPlugin: FastifyPluginAsync = async (fastify) => { + const pubClient = new Redis({ + host: process.env.REDIS_HOST, + port: parseInt(process.env.REDIS_PORT || '6379'), + }) + + const subClient = pubClient.duplicate() + + const io = new Server(fastify.server, { + cors: { + origin: process.env.ALLOWED_ORIGINS?.split(','), + credentials: true, + }, + adapter: createAdapter(pubClient, subClient), + }) + + // Authentication middleware + io.use(async (socket, next) => { + const token = socket.handshake.auth.token + + try { + const decoded = await fastify.jwt.verify(token) + const user = await fastify.prisma.user.findUnique({ + where: { keycloakId: decoded.sub } + }) + + if (!user) throw new Error('User not found') + + socket.data.user = user + next() + } catch (err) { + next(new Error('Authentication failed')) + } + }) + + io.on('connection', (socket) => { + const user = socket.data.user + + // Join user room + socket.join(`user:${user.id}`) + + // Join role-based rooms + socket.join(`role:${user.role}`) + + // Handle booking updates + socket.on('booking:subscribe', async (bookingId) => { + // Verify user has access to this booking + const booking = await fastify.prisma.booking.findFirst({ + where: { + id: bookingId, + OR: [ + { userId: user.id }, + { yacht: { ownerId: user.id } }, + ] + } + }) + + if (booking) { + socket.join(`booking:${bookingId}`) + } + }) + + // Handle yacht tracking + socket.on('yacht:track', async (yachtId) => { + socket.join(`yacht:${yachtId}:tracking`) + + // Send initial position + const position = await getYachtPosition(yachtId) + socket.emit('yacht:position', position) + }) + + // Handle chat messages + socket.on('message:send', async (data) => { + const message = await fastify.prisma.message.create({ + data: { + bookingId: data.bookingId, + senderId: user.id, + content: data.content, + attachments: data.attachments || [], + }, + include: { + sender: true, + } + }) + + // Send to all participants + io.to(`booking:${data.bookingId}`).emit('message:new', message) + + // Send push notification to recipient + await sendPushNotification(data.recipientId, { + title: 'New message', + body: data.content, + data: { bookingId: data.bookingId } + }) + }) + + // Handle typing indicators + socket.on('typing:start', ({ bookingId }) => { + socket.to(`booking:${bookingId}`).emit('typing:user', { + userId: user.id, + name: `${user.firstName} ${user.lastName}`, + }) + }) + + socket.on('typing:stop', ({ bookingId }) => { + socket.to(`booking:${bookingId}`).emit('typing:user:stop', { + userId: user.id, + }) + }) + + // Handle disconnection + socket.on('disconnect', () => { + // Clean up any typing indicators + io.emit('typing:user:stop', { userId: user.id }) + }) + }) + + // Expose io instance for use in routes + fastify.decorate('io', io) +} +``` + +## Deployment Strategy + +### Overview + +The Harborsmith platform is deployed using Docker containers orchestrated with Docker Compose, running behind an nginx reverse proxy on the host server. This architecture provides isolation, scalability, and easy management while leveraging the existing nginx infrastructure for SSL termination and load balancing. + +### Host Nginx Configuration + +Since nginx is already running on the host server, we'll configure it as the main reverse proxy for all Harborsmith services. This provides centralized SSL management, load balancing, and request routing. + +```nginx +# /etc/nginx/sites-available/harborsmith.conf + +# Upstream definitions for load balancing +upstream harborsmith_website { + least_conn; + server 127.0.0.1:3001 max_fails=3 fail_timeout=30s; + server 127.0.0.1:3002 max_fails=3 fail_timeout=30s; + keepalive 32; +} + +upstream harborsmith_webapp { + least_conn; + server 127.0.0.1:3003 max_fails=3 fail_timeout=30s; + server 127.0.0.1:3004 max_fails=3 fail_timeout=30s; + keepalive 32; +} + +upstream harborsmith_portal { + least_conn; + server 127.0.0.1:3005 max_fails=3 fail_timeout=30s; + server 127.0.0.1:3006 max_fails=3 fail_timeout=30s; + keepalive 32; +} + +upstream harborsmith_api { + ip_hash; # Sticky sessions for WebSocket support + server 127.0.0.1:3000 max_fails=3 fail_timeout=30s; + server 127.0.0.1:3010 max_fails=3 fail_timeout=30s; + server 127.0.0.1:3020 max_fails=3 fail_timeout=30s; + keepalive 64; +} + +# MinIO is external - no upstream needed as it has its own access point + +# Rate limiting zones +limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; +limit_req_zone $binary_remote_addr zone=upload_limit:10m rate=2r/s; + +# Main website (SSG) +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name harborsmith.com www.harborsmith.com; + + # SSL configuration (adjust paths as needed) + ssl_certificate /etc/letsencrypt/live/harborsmith.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/harborsmith.com/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + ssl_stapling on; + ssl_stapling_verify on; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Content-Security-Policy "default-src 'self' https:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:;" always; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1000; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + location / { + proxy_pass http://harborsmith_website; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + + # Cache static assets + location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ { + proxy_pass http://harborsmith_website; + expires 30d; + add_header Cache-Control "public, immutable"; + } + } +} + +# Customer Web App (SPA) +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name app.harborsmith.com; + + ssl_certificate /etc/letsencrypt/live/harborsmith.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/harborsmith.com/privkey.pem; + + # Inherit SSL settings from snippets/ssl-params.conf if available + # include snippets/ssl-params.conf; + + location / { + proxy_pass http://harborsmith_webapp; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + } +} + +# Admin Portal +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name portal.harborsmith.com; + + ssl_certificate /etc/letsencrypt/live/harborsmith.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/harborsmith.com/privkey.pem; + + location / { + proxy_pass http://harborsmith_portal; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + } +} + +# API Server +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name api.harborsmith.com; + + ssl_certificate /etc/letsencrypt/live/harborsmith.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/harborsmith.com/privkey.pem; + + # API rate limiting + location / { + limit_req zone=api_limit burst=20 nodelay; + + proxy_pass http://harborsmith_api; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + + # Timeouts for long-running requests + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # WebSocket endpoint + location /ws { + proxy_pass http://harborsmith_api; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket timeouts + proxy_connect_timeout 7d; + proxy_send_timeout 7d; + proxy_read_timeout 7d; + } + + # Upload endpoint with larger limits + location /upload { + limit_req zone=upload_limit burst=5 nodelay; + client_max_body_size 10G; + client_body_timeout 3600s; + + proxy_pass http://harborsmith_api; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Extended timeouts for large uploads + proxy_connect_timeout 3600s; + proxy_send_timeout 3600s; + proxy_read_timeout 3600s; + proxy_request_buffering off; + } +} + +# MinIO is already accessible via its own endpoint/domain +# If you need to proxy MinIO through this nginx (optional): +# server { +# listen 443 ssl http2; +# listen [::]:443 ssl http2; +# server_name minio.harborsmith.com; +# +# ssl_certificate /etc/letsencrypt/live/harborsmith.com/fullchain.pem; +# ssl_certificate_key /etc/letsencrypt/live/harborsmith.com/privkey.pem; +# +# ignore_invalid_headers off; +# client_max_body_size 0; +# proxy_buffering off; +# +# location / { +# proxy_set_header Host $http_host; +# proxy_set_header X-Real-IP $remote_addr; +# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +# proxy_set_header X-Forwarded-Proto $scheme; +# +# proxy_connect_timeout 300; +# proxy_http_version 1.1; +# proxy_set_header Connection ""; +# chunked_transfer_encoding off; +# +# # Replace with actual MinIO endpoint +# proxy_pass http://${MINIO_EXTERNAL_HOST}:${MINIO_EXTERNAL_PORT}; +# } +# } + +# HTTP to HTTPS redirect +server { + listen 80; + listen [::]:80; + server_name harborsmith.com www.harborsmith.com app.harborsmith.com portal.harborsmith.com api.harborsmith.com minio.harborsmith.com; + return 301 https://$server_name$request_uri; +} +``` + +### Docker Configuration + +#### Docker Compose Configuration + +```yaml +# docker-compose.yml +version: '3.9' + +services: + # Database + postgres: + image: postgis/postgis:16-3.4 + container_name: harborsmith_postgres + restart: unless-stopped + environment: + POSTGRES_DB: harborsmith + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_MAX_CONNECTIONS: 200 + POSTGRES_SHARED_BUFFERS: 256MB + volumes: + - postgres_data:/var/lib/postgresql/data + - ./infrastructure/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql:ro + ports: + - "127.0.0.1:5432:5432" # Only expose to localhost + networks: + - harborsmith_internal + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d harborsmith"] + interval: 10s + timeout: 5s + retries: 5 + + # Cache + redis: + image: redis:7-alpine + container_name: harborsmith_redis + restart: unless-stopped + command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru + volumes: + - redis_data:/data + - ./infrastructure/redis/redis.conf:/usr/local/etc/redis/redis.conf:ro + ports: + - "127.0.0.1:6379:6379" # Only expose to localhost + networks: + - harborsmith_internal + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + # Database Connection Pooler + pgbouncer: + image: pgbouncer/pgbouncer:latest + container_name: harborsmith_pgbouncer + restart: unless-stopped + environment: + DATABASES_HOST: postgres + DATABASES_PORT: 5432 + DATABASES_DBNAME: harborsmith + DATABASES_USER: ${DB_USER} + DATABASES_PASSWORD: ${DB_PASSWORD} + POOL_MODE: transaction + MAX_CLIENT_CONN: 1000 + DEFAULT_POOL_SIZE: 25 + MIN_POOL_SIZE: 5 + RESERVE_POOL_SIZE: 5 + ports: + - "127.0.0.1:6432:6432" # PgBouncer port + networks: + - harborsmith_internal + depends_on: + postgres: + condition: service_healthy + + # MinIO is already running in a separate Docker Compose stack + # Connection details will be provided via environment variables: + # - MINIO_ENDPOINT: External MinIO endpoint (e.g., minio.local or IP address) + # - MINIO_PORT: External MinIO port (default: 9000) + # - MINIO_ACCESS_KEY: Access key for existing MinIO instance + # - MINIO_SECRET_KEY: Secret key for existing MinIO instance + + # API Service (3 replicas) + api-1: + build: + context: . + dockerfile: apps/api/Dockerfile + container_name: harborsmith_api_1 + restart: unless-stopped + environment: + NODE_ENV: production + PORT: 3000 + DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/harborsmith + REDIS_URL: redis://redis:6379 + MINIO_ENDPOINT: ${MINIO_ENDPOINT} # External MinIO endpoint + MINIO_PORT: ${MINIO_PORT:-9000} + MINIO_USE_SSL: ${MINIO_USE_SSL:-false} + MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} + MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} + KEYCLOAK_URL: ${KEYCLOAK_URL} + STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY} + CAL_API_KEY: ${CAL_API_KEY} + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + # MinIO is external - no dependency needed + ports: + - "127.0.0.1:3000:3000" + networks: + - harborsmith_internal + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/health"] + interval: 30s + timeout: 10s + retries: 3 + + api-2: + build: + context: . + dockerfile: apps/api/Dockerfile + container_name: harborsmith_api_2 + restart: unless-stopped + environment: + NODE_ENV: production + PORT: 3000 + DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/harborsmith + REDIS_URL: redis://redis:6379 + MINIO_ENDPOINT: ${MINIO_ENDPOINT} # External MinIO endpoint + MINIO_PORT: ${MINIO_PORT:-9000} + MINIO_USE_SSL: ${MINIO_USE_SSL:-false} + MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} + MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} + KEYCLOAK_URL: ${KEYCLOAK_URL} + STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY} + CAL_API_KEY: ${CAL_API_KEY} + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + # MinIO is external - no dependency needed + ports: + - "127.0.0.1:3010:3000" + networks: + - harborsmith_internal + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/health"] + interval: 30s + timeout: 10s + retries: 3 + + api-3: + build: + context: . + dockerfile: apps/api/Dockerfile + container_name: harborsmith_api_3 + restart: unless-stopped + environment: + NODE_ENV: production + PORT: 3000 + DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/harborsmith + REDIS_URL: redis://redis:6379 + MINIO_ENDPOINT: ${MINIO_ENDPOINT} # External MinIO endpoint + MINIO_PORT: ${MINIO_PORT:-9000} + MINIO_USE_SSL: ${MINIO_USE_SSL:-false} + MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} + MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} + KEYCLOAK_URL: ${KEYCLOAK_URL} + STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY} + CAL_API_KEY: ${CAL_API_KEY} + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + # MinIO is external - no dependency needed + ports: + - "127.0.0.1:3020:3000" + networks: + - harborsmith_internal + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/health"] + interval: 30s + timeout: 10s + retries: 3 + + # Frontend Services + website-1: + build: + context: . + dockerfile: apps/website/Dockerfile + args: + - APP=website + container_name: harborsmith_website_1 + restart: unless-stopped + environment: + NODE_ENV: production + NUXT_PUBLIC_API_URL: https://api.harborsmith.com + NUXT_PUBLIC_WS_URL: wss://api.harborsmith.com/ws + ports: + - "127.0.0.1:3001:3000" + networks: + - harborsmith_internal + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000"] + interval: 30s + timeout: 10s + retries: 3 + + website-2: + build: + context: . + dockerfile: apps/website/Dockerfile + args: + - APP=website + container_name: harborsmith_website_2 + restart: unless-stopped + environment: + NODE_ENV: production + NUXT_PUBLIC_API_URL: https://api.harborsmith.com + NUXT_PUBLIC_WS_URL: wss://api.harborsmith.com/ws + ports: + - "127.0.0.1:3002:3000" + networks: + - harborsmith_internal + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000"] + interval: 30s + timeout: 10s + retries: 3 + + webapp-1: + build: + context: . + dockerfile: apps/webapp/Dockerfile + args: + - APP=webapp + container_name: harborsmith_webapp_1 + restart: unless-stopped + environment: + NODE_ENV: production + NUXT_PUBLIC_API_URL: https://api.harborsmith.com + NUXT_PUBLIC_WS_URL: wss://api.harborsmith.com/ws + NUXT_PUBLIC_KEYCLOAK_URL: ${KEYCLOAK_URL} + NUXT_PUBLIC_KEYCLOAK_REALM: ${KEYCLOAK_REALM} + NUXT_PUBLIC_KEYCLOAK_CLIENT_ID: ${KEYCLOAK_CLIENT_ID} + ports: + - "127.0.0.1:3003:3000" + networks: + - harborsmith_internal + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000"] + interval: 30s + timeout: 10s + retries: 3 + + webapp-2: + build: + context: . + dockerfile: apps/webapp/Dockerfile + args: + - APP=webapp + container_name: harborsmith_webapp_2 + restart: unless-stopped + environment: + NODE_ENV: production + NUXT_PUBLIC_API_URL: https://api.harborsmith.com + NUXT_PUBLIC_WS_URL: wss://api.harborsmith.com/ws + NUXT_PUBLIC_KEYCLOAK_URL: ${KEYCLOAK_URL} + NUXT_PUBLIC_KEYCLOAK_REALM: ${KEYCLOAK_REALM} + NUXT_PUBLIC_KEYCLOAK_CLIENT_ID: ${KEYCLOAK_CLIENT_ID} + ports: + - "127.0.0.1:3004:3000" + networks: + - harborsmith_internal + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000"] + interval: 30s + timeout: 10s + retries: 3 + + portal-1: + build: + context: . + dockerfile: apps/portal/Dockerfile + args: + - APP=portal + container_name: harborsmith_portal_1 + restart: unless-stopped + environment: + NODE_ENV: production + NUXT_PUBLIC_API_URL: https://api.harborsmith.com + NUXT_PUBLIC_WS_URL: wss://api.harborsmith.com/ws + NUXT_PUBLIC_KEYCLOAK_URL: ${KEYCLOAK_URL} + NUXT_PUBLIC_KEYCLOAK_REALM: ${KEYCLOAK_REALM} + NUXT_PUBLIC_KEYCLOAK_CLIENT_ID: ${KEYCLOAK_ADMIN_CLIENT_ID} + ports: + - "127.0.0.1:3005:3000" + networks: + - harborsmith_internal + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000"] + interval: 30s + timeout: 10s + retries: 3 + + portal-2: + build: + context: . + dockerfile: apps/portal/Dockerfile + args: + - APP=portal + container_name: harborsmith_portal_2 + restart: unless-stopped + environment: + NODE_ENV: production + NUXT_PUBLIC_API_URL: https://api.harborsmith.com + NUXT_PUBLIC_WS_URL: wss://api.harborsmith.com/ws + NUXT_PUBLIC_KEYCLOAK_URL: ${KEYCLOAK_URL} + NUXT_PUBLIC_KEYCLOAK_REALM: ${KEYCLOAK_REALM} + NUXT_PUBLIC_KEYCLOAK_CLIENT_ID: ${KEYCLOAK_ADMIN_CLIENT_ID} + ports: + - "127.0.0.1:3006:3000" + networks: + - harborsmith_internal + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000"] + interval: 30s + timeout: 10s + retries: 3 + +volumes: + postgres_data: + driver: local + redis_data: + driver: local + # MinIO data is managed by the external MinIO instance + +networks: + harborsmith_internal: + name: harborsmith_internal + driver: bridge + ipam: + config: + - subnet: 172.20.0.0/16 +``` + +#### Dockerfiles for Each Service + +```dockerfile +# apps/api/Dockerfile +FROM node:20-alpine AS base +RUN apk add --no-cache libc6-compat ffmpeg +WORKDIR /app + +# Install dependencies +FROM base AS deps +COPY package*.json ./ +COPY turbo.json ./ +COPY packages/database/package.json ./packages/database/ +COPY packages/shared/package.json ./packages/shared/ +RUN npm ci + +# Build the application +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN npx turbo run build --filter=@harborsmith/api + +# Production image +FROM base AS runner +ENV NODE_ENV production + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nodejs + +COPY --from=builder --chown=nodejs:nodejs /app/apps/api/dist ./dist +COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules +COPY --from=builder --chown=nodejs:nodejs /app/packages/database/prisma ./prisma + +USER nodejs +EXPOSE 3000 + +CMD ["node", "dist/server.js"] +``` + +```dockerfile +# apps/website/Dockerfile (SSG) +FROM node:20-alpine AS base +RUN apk add --no-cache libc6-compat +WORKDIR /app + +FROM base AS deps +COPY package*.json ./ +COPY turbo.json ./ +RUN npm ci + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN npx turbo run build --filter=@harborsmith/website + +FROM base AS runner +ENV NODE_ENV production + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nodejs + +COPY --from=builder --chown=nodejs:nodejs /app/apps/website/.output ./.output +COPY --from=builder --chown=nodejs:nodejs /app/apps/website/public ./public + +USER nodejs +EXPOSE 3000 + +CMD ["node", ".output/server/index.mjs"] +``` + +```dockerfile +# apps/webapp/Dockerfile (SPA) +FROM node:20-alpine AS base +RUN apk add --no-cache libc6-compat +WORKDIR /app + +FROM base AS deps +COPY package*.json ./ +COPY turbo.json ./ +RUN npm ci + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN npx turbo run build --filter=@harborsmith/webapp + +FROM nginx:alpine AS runner +COPY --from=builder /app/apps/webapp/.output/public /usr/share/nginx/html +COPY ./infrastructure/nginx/spa.conf /etc/nginx/conf.d/default.conf + +EXPOSE 3000 +CMD ["nginx", "-g", "daemon off;"] +``` + +### Deployment Scripts + +```bash +#!/bin/bash +# deploy.sh - Main deployment script + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${GREEN}Starting Harborsmith deployment...${NC}" + +# Check for required environment variables +required_vars=( + "DB_USER" + "DB_PASSWORD" + "MINIO_ACCESS_KEY" + "MINIO_SECRET_KEY" + "KEYCLOAK_URL" + "STRIPE_SECRET_KEY" + "CAL_API_KEY" +) + +for var in "${required_vars[@]}"; do + if [[ -z "${!var}" ]]; then + echo -e "${RED}Error: $var is not set${NC}" + exit 1 + fi +done + +# Pull latest code +echo -e "${YELLOW}Pulling latest code...${NC}" +git pull origin main + +# Build containers +echo -e "${YELLOW}Building containers...${NC}" +docker-compose build --parallel + +# Run database migrations +echo -e "${YELLOW}Running database migrations...${NC}" +docker-compose run --rm api npx prisma migrate deploy + +# Start services +echo -e "${YELLOW}Starting services...${NC}" +docker-compose up -d + +# Wait for health checks +echo -e "${YELLOW}Waiting for services to be healthy...${NC}" +./scripts/wait-for-healthy.sh + +# Reload nginx configuration +echo -e "${YELLOW}Reloading nginx configuration...${NC}" +sudo nginx -t && sudo nginx -s reload + +echo -e "${GREEN}Deployment complete!${NC}" +``` + +```bash +#!/bin/bash +# scripts/wait-for-healthy.sh + +services=( + "harborsmith_postgres" + "harborsmith_redis" + "harborsmith_pgbouncer" + "harborsmith_api_1" + "harborsmith_api_2" + "harborsmith_api_3" + "harborsmith_website_1" + "harborsmith_webapp_1" + "harborsmith_portal_1" +) + +# Check external MinIO connectivity +echo "Checking MinIO connectivity..." +curl -f http://${MINIO_ENDPOINT}:${MINIO_PORT}/minio/health/live || { + echo "WARNING: Cannot reach external MinIO instance at ${MINIO_ENDPOINT}:${MINIO_PORT}" + echo "Please ensure MinIO is running and accessible" +} + +for service in "${services[@]}"; do + echo "Waiting for $service to be healthy..." + while [ "$(docker inspect -f '{{.State.Health.Status}}' $service 2>/dev/null)" != "healthy" ]; do + sleep 2 + done + echo "$service is healthy!" +done +``` + +### Container Management + +```bash +# Start all services +docker-compose up -d + +# Stop all services +docker-compose down + +# View logs +docker-compose logs -f [service_name] + +# Scale a service +docker-compose up -d --scale api=5 + +# Update a single service +docker-compose up -d --no-deps --build api-1 + +# Database backup +docker exec harborsmith_postgres pg_dump -U $DB_USER harborsmith > backup_$(date +%Y%m%d).sql + +# Database restore +docker exec -i harborsmith_postgres psql -U $DB_USER harborsmith < backup.sql +``` + +### Environment Configuration + +```bash +# .env file (root directory) +# Database +DB_USER=harborsmith +DB_PASSWORD=your_secure_password_here + +# External MinIO Configuration +MINIO_ENDPOINT=minio.example.com # or IP address of MinIO host +MINIO_PORT=9000 +MINIO_USE_SSL=true # Set to true if MinIO uses HTTPS +MINIO_ACCESS_KEY=your_external_minio_access_key +MINIO_SECRET_KEY=your_external_minio_secret_key + +# External Services +KEYCLOAK_URL=https://auth.harborsmith.com +KEYCLOAK_REALM=harborsmith +KEYCLOAK_CLIENT_ID=harborsmith-webapp +KEYCLOAK_ADMIN_CLIENT_ID=harborsmith-portal + +# Stripe +STRIPE_SECRET_KEY=sk_live_your_stripe_key + +# Cal.com +CAL_API_KEY=cal_live_your_cal_key + +# Application +NODE_ENV=production +LOG_LEVEL=info +ALLOWED_ORIGINS=https://harborsmith.com,https://app.harborsmith.com,https://portal.harborsmith.com +``` + +### External MinIO Integration + +Since MinIO is running in a separate Docker Compose stack, the Harborsmith services connect to it via network configuration: + +#### MinIO Connection Configuration +```typescript +// apps/api/src/config/storage.ts +import { Client } from 'minio' + +export const minioClient = new Client({ + endPoint: process.env.MINIO_ENDPOINT!, // External MinIO host + port: parseInt(process.env.MINIO_PORT || '9000'), + useSSL: process.env.MINIO_USE_SSL === 'true', + accessKey: process.env.MINIO_ACCESS_KEY!, + secretKey: process.env.MINIO_SECRET_KEY!, +}) + +// Initialize buckets on startup +export async function initializeStorage() { + const buckets = ['uploads', 'media', 'backups', 'documents'] + + for (const bucket of buckets) { + const exists = await minioClient.bucketExists(bucket) + if (!exists) { + await minioClient.makeBucket(bucket, 'us-west-2') + console.log(`Created bucket: ${bucket}`) + } + } + + // Set bucket policies + const publicPolicy = { + Version: '2012-10-17', + Statement: [{ + Effect: 'Allow', + Principal: { AWS: ['*'] }, + Action: ['s3:GetObject'], + Resource: ['arn:aws:s3:::media/*'], + }], + } + + await minioClient.setBucketPolicy('media', JSON.stringify(publicPolicy)) +} +``` + +#### Network Connectivity Requirements +- MinIO must be accessible from Docker containers +- If MinIO is on the same host: use host networking or bridge network +- If MinIO is on different host: ensure firewall allows port 9000 +- For production: Use internal network IP or hostname + +#### Docker Network Setup (if MinIO on same host) +```bash +# Create shared network for MinIO communication +docker network create minio-shared + +# Add to MinIO compose file +networks: + default: + external: + name: minio-shared + +# Add to Harborsmith compose file +networks: + harborsmith_internal: + driver: bridge + minio-shared: + external: true + +# Update service definitions to use both networks +services: + api-1: + networks: + - harborsmith_internal + - minio-shared +``` + +### Production Considerations + +1. **SSL Certificates**: Using Let's Encrypt with nginx on the host +2. **Container Networking**: All containers expose ports only to localhost (127.0.0.1) +3. **Health Checks**: Each service has health check endpoints +4. **External MinIO**: Connected via environment variables, ensure network connectivity +5. **Logging**: Centralized logging through Docker's logging driver +6. **Monitoring**: Integration with monitoring stack (Prometheus/Grafana) +7. **Backups**: Automated PostgreSQL backups, MinIO handled by external instance +8. **Updates**: Zero-downtime deployments using rolling updates + +## Security Architecture + +### Security Best Practices + +1. **Authentication & Authorization** + - OAuth2/OIDC via Keycloak + - JWT tokens with short expiration + - Refresh token rotation + - MFA support + +2. **Data Protection** + - Encryption at rest (PostgreSQL TDE) + - Encryption in transit (TLS 1.3) + - Field-level encryption for PII + - GDPR compliance + +3. **API Security** + - Rate limiting per user/IP + - Input validation with Zod + - SQL injection prevention (Prisma) + - XSS protection (CSP headers) + +4. **Infrastructure Security** + - Network segmentation + - Secrets management (Vault) + - Regular security updates + - Container scanning + +5. **Monitoring & Compliance** + - Audit logging + - Intrusion detection + - SIEM integration + - Compliance reporting + +## Performance Optimization + +### Frontend Optimizations + +1. **Bundle Size** + - Tree shaking + - Code splitting + - Dynamic imports + - Component lazy loading + +2. **Caching Strategy** + - Browser caching + - CDN caching + - API response caching + - Static asset optimization + +3. **Image Optimization** + - Next-gen formats (WebP, AVIF) + - Responsive images + - Lazy loading + - CDN delivery + +### Backend Optimizations + +1. **Database** + - Connection pooling + - Query optimization + - Indexes on foreign keys + - Materialized views for reports + +2. **Caching** + - Redis for session storage + - Query result caching + - Full-page caching for SSG + - CDN for static assets + +3. **Scaling** + - Horizontal scaling with load balancer + - Database read replicas + - Microservices for heavy operations + - Queue-based processing + +## Monitoring & Observability + +### Logging Strategy + +```typescript +// Structured logging with Pino +const logger = pino({ + level: process.env.LOG_LEVEL || 'info', + transport: { + targets: [ + { + target: 'pino-pretty', + options: { colorize: true }, + level: 'debug' + }, + { + target: '@axiomhq/pino', + options: { + dataset: process.env.AXIOM_DATASET, + token: process.env.AXIOM_TOKEN, + }, + level: 'info' + } + ] + } +}) +``` + +### Metrics Collection + +```typescript +// Prometheus metrics +import { register, Counter, Histogram, Gauge } from 'prom-client' + +const httpRequestDuration = new Histogram({ + name: 'http_request_duration_seconds', + help: 'Duration of HTTP requests in seconds', + labelNames: ['method', 'route', 'status'] +}) + +const activeBookings = new Gauge({ + name: 'active_bookings_total', + help: 'Total number of active bookings', +}) + +const paymentProcessed = new Counter({ + name: 'payments_processed_total', + help: 'Total number of processed payments', + labelNames: ['status', 'method'] +}) +``` + +## Critical Improvements & Best Practices + +### 1. Database Optimization + +#### Connection Pooling with PgBouncer +- Added PgBouncer service for connection pooling +- Transaction pooling mode for optimal performance +- Prevents connection exhaustion under high load + +#### Database Configuration +```sql +-- infrastructure/postgres/init.sql +-- Performance optimizations +ALTER SYSTEM SET shared_buffers = '256MB'; +ALTER SYSTEM SET effective_cache_size = '1GB'; +ALTER SYSTEM SET maintenance_work_mem = '64MB'; +ALTER SYSTEM SET checkpoint_completion_target = 0.9; +ALTER SYSTEM SET wal_buffers = '16MB'; +ALTER SYSTEM SET default_statistics_target = 100; +ALTER SYSTEM SET random_page_cost = 1.1; +ALTER SYSTEM SET effective_io_concurrency = 200; +ALTER SYSTEM SET work_mem = '4MB'; +ALTER SYSTEM SET min_wal_size = '1GB'; +ALTER SYSTEM SET max_wal_size = '4GB'; + +-- Create indexes for foreign keys and common queries +CREATE INDEX idx_bookings_yacht_dates ON bookings(yacht_id, start_date, end_date); +CREATE INDEX idx_bookings_user ON bookings(user_id); +CREATE INDEX idx_bookings_status ON bookings(status, payment_status); +CREATE INDEX idx_yachts_location ON yachts USING gin(location); +CREATE INDEX idx_yachts_features ON yachts USING gin(features); +CREATE INDEX idx_media_yacht ON media(yacht_id, type, is_primary); +CREATE INDEX idx_reviews_yacht ON reviews(yacht_id, status); +CREATE INDEX idx_activity_log_entity ON activity_log(entity_type, entity_id); +CREATE INDEX idx_activity_log_created ON activity_log(created_at DESC); +``` + +### 2. Enhanced Security Configuration + +#### CORS Configuration +```typescript +// apps/api/src/config/cors.ts +export const corsConfig = { + origin: (origin, callback) => { + const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [] + + // Allow requests with no origin (mobile apps, Postman) + if (!origin) return callback(null, true) + + // Check if origin is allowed + if (allowedOrigins.includes(origin)) { + callback(null, true) + } else { + callback(new Error('Not allowed by CORS')) + } + }, + credentials: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'], + exposedHeaders: ['X-Total-Count', 'X-Page-Count'], + maxAge: 86400, // 24 hours +} +``` + +#### Application-Level Rate Limiting +```typescript +// apps/api/src/plugins/rate-limiter.ts +import { FastifyPluginAsync } from 'fastify' +import { RateLimiterRedis, RateLimiterRes } from 'rate-limiter-flexible' + +export const rateLimiterPlugin: FastifyPluginAsync = async (fastify) => { + const rateLimiter = new RateLimiterRedis({ + storeClient: fastify.redis, + keyPrefix: 'rl', + points: 100, // Number of requests + duration: 60, // Per 60 seconds + blockDuration: 60 * 10, // Block for 10 minutes + }) + + // Different limits for different endpoints + const limiters = { + api: new RateLimiterRedis({ + storeClient: fastify.redis, + keyPrefix: 'rl:api', + points: 100, + duration: 60, + }), + auth: new RateLimiterRedis({ + storeClient: fastify.redis, + keyPrefix: 'rl:auth', + points: 5, + duration: 60 * 15, // 5 attempts per 15 minutes + }), + upload: new RateLimiterRedis({ + storeClient: fastify.redis, + keyPrefix: 'rl:upload', + points: 10, + duration: 60 * 60, // 10 uploads per hour + }), + } + + fastify.addHook('onRequest', async (request, reply) => { + try { + const limiter = getLimiterForRoute(request.url) + await limiter.consume(request.ip) + } catch (rejRes) { + reply.code(429).send({ + error: 'Too Many Requests', + retryAfter: Math.round(rejRes.msBeforeNext / 1000) || 60, + }) + } + }) +} +``` + +### 3. WebSocket Scaling Solution + +#### Redis Adapter for Socket.io +```typescript +// apps/api/src/plugins/socket-scaled.ts +import { createAdapter } from '@socket.io/redis-adapter' +import { createClient } from 'redis' + +export const scaledSocketPlugin: FastifyPluginAsync = async (fastify) => { + const pubClient = createClient({ url: process.env.REDIS_URL }) + const subClient = pubClient.duplicate() + + await Promise.all([ + pubClient.connect(), + subClient.connect(), + ]) + + const io = new Server(fastify.server, { + cors: corsConfig, + adapter: createAdapter(pubClient, subClient), + connectionStateRecovery: { + maxDisconnectionDuration: 2 * 60 * 1000, // 2 minutes + skipMiddlewares: true, + }, + }) + + // Session affinity handled by nginx ip_hash + io.on('connection', (socket) => { + // Connection handling... + }) +} +``` + +### 4. Comprehensive Caching Strategy + +```typescript +// packages/shared/src/cache/cache-manager.ts +export class CacheManager { + private redis: Redis + private strategies: Map + + constructor(redis: Redis) { + this.redis = redis + this.strategies = new Map([ + ['yacht-list', { ttl: 300, pattern: 'cache-aside' }], + ['yacht-detail', { ttl: 3600, pattern: 'cache-aside' }], + ['user-session', { ttl: 86400, pattern: 'write-through' }], + ['booking-availability', { ttl: 60, pattern: 'cache-aside' }], + ]) + } + + async get(key: string, fetcher?: () => Promise): Promise { + const cached = await this.redis.get(key) + + if (cached) { + return JSON.parse(cached) + } + + if (!fetcher) return null + + // Cache-aside pattern + const data = await fetcher() + const strategy = this.getStrategy(key) + + await this.set(key, data, strategy.ttl) + return data + } + + async set(key: string, value: any, ttl?: number): Promise { + const strategy = this.getStrategy(key) + const finalTtl = ttl || strategy.ttl + + await this.redis.setex(key, finalTtl, JSON.stringify(value)) + + // Emit cache invalidation event + await this.redis.publish('cache:invalidate', JSON.stringify({ key, ttl: finalTtl })) + } + + async invalidate(pattern: string): Promise { + const keys = await this.redis.keys(pattern) + if (keys.length > 0) { + await this.redis.del(...keys) + } + } + + async warmUp(): Promise { + // Pre-load frequently accessed data + const popularYachts = await this.redis.zrevrange('popular:yachts', 0, 10) + + for (const yachtId of popularYachts) { + await this.get(`yacht:${yachtId}`, async () => { + return await prisma.yacht.findUnique({ where: { id: yachtId } }) + }) + } + } +} +``` + +### 5. CDN & Media Optimization + +#### CloudFlare CDN Configuration +```typescript +// apps/api/src/services/cdn.service.ts +export class CDNService { + private cloudflare: Cloudflare + + constructor() { + this.cloudflare = new Cloudflare({ + email: process.env.CLOUDFLARE_EMAIL, + key: process.env.CLOUDFLARE_API_KEY, + }) + } + + async purgeCache(urls: string[]): Promise { + await this.cloudflare.zones.purgeCache( + process.env.CLOUDFLARE_ZONE_ID, + { files: urls } + ) + } + + async uploadToR2(file: Buffer, key: string): Promise { + // Upload to Cloudflare R2 for edge storage + const url = await this.cloudflare.r2.upload(file, key) + return `https://cdn.harborsmith.com/${key}` + } +} +``` + +#### Image Optimization Pipeline +```typescript +// apps/api/src/workers/image.processor.ts +import sharp from 'sharp' + +export async function processImage(input: Buffer): Promise { + const variants = [ + { name: 'thumbnail', width: 150, height: 150, quality: 80 }, + { name: 'small', width: 400, height: 300, quality: 85 }, + { name: 'medium', width: 800, height: 600, quality: 85 }, + { name: 'large', width: 1920, height: 1080, quality: 90 }, + ] + + const processed = await Promise.all( + variants.map(async (variant) => { + const webp = await sharp(input) + .resize(variant.width, variant.height, { fit: 'cover' }) + .webp({ quality: variant.quality }) + .toBuffer() + + const avif = await sharp(input) + .resize(variant.width, variant.height, { fit: 'cover' }) + .avif({ quality: variant.quality - 5 }) + .toBuffer() + + return { ...variant, webp, avif } + }) + ) + + // Generate blur placeholder + const placeholder = await sharp(input) + .resize(20, 20, { fit: 'cover' }) + .blur(10) + .toBuffer() + + return { variants: processed, placeholder } +} +``` + +### 6. CI/CD Pipeline + +#### GitHub Actions Workflow +```yaml +# .github/workflows/main.yml +name: CI/CD Pipeline + +on: + push: + branches: [main, staging] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + env: + POSTGRES_PASSWORD: test + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + redis: + image: redis:7 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run linter + run: npm run lint + + - name: Run type check + run: npm run type-check + + - name: Run unit tests + run: npm run test:unit -- --coverage + + - name: Run integration tests + run: npm run test:integration + + - name: Run E2E tests + run: npm run test:e2e + + - name: SonarCloud Scan + uses: SonarSource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + - name: Upload coverage + uses: codecov/codecov-action@v3 + + security: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Run Snyk Security Scan + uses: snyk/actions/node@master + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + scan-ref: '.' + + build: + needs: [test, security] + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Build and push Docker images + run: | + docker-compose build --parallel + docker-compose push + + deploy: + needs: build + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - name: Deploy to production + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.PRODUCTION_HOST }} + username: ${{ secrets.PRODUCTION_USER }} + key: ${{ secrets.PRODUCTION_SSH_KEY }} + script: | + cd /opt/harborsmith + git pull origin main + docker-compose pull + docker-compose up -d --remove-orphans + ./scripts/wait-for-healthy.sh + sudo nginx -s reload +``` + +### 7. Testing Strategy + +#### Test Configuration +```typescript +// vitest.config.ts +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + exclude: [ + 'node_modules/', + 'dist/', + '*.config.ts', + ], + lines: 80, + functions: 80, + branches: 80, + statements: 80, + }, + setupFiles: ['./tests/setup.ts'], + }, +}) +``` + +#### E2E Testing with Playwright +```typescript +// tests/e2e/booking-flow.spec.ts +import { test, expect } from '@playwright/test' + +test.describe('Booking Flow', () => { + test('should complete a yacht booking', async ({ page }) => { + // Login + await page.goto('/login') + await page.fill('[name=email]', 'test@example.com') + await page.fill('[name=password]', 'password') + await page.click('button[type=submit]') + + // Search for yacht + await page.goto('/yachts') + await page.fill('[name=search]', 'Sunset Dream') + await page.click('button[aria-label="Search"]') + + // Select yacht + await page.click('[data-yacht-id="123"]') + await expect(page).toHaveURL(/\/yachts\/123/) + + // Select dates + await page.click('[data-testid="date-picker"]') + await page.click('[data-date="2024-06-15"]') + await page.click('[data-date="2024-06-17"]') + + // Add extras + await page.check('[name="extras.catering"]') + await page.check('[name="extras.captain"]') + + // Proceed to payment + await page.click('button:has-text("Book Now")') + + // Fill payment details (Stripe Elements) + const stripeFrame = page.frameLocator('iframe[name*="stripe"]') + await stripeFrame.locator('[name="cardnumber"]').fill('4242424242424242') + await stripeFrame.locator('[name="exp-date"]').fill('12/25') + await stripeFrame.locator('[name="cvc"]').fill('123') + + // Confirm booking + await page.click('button:has-text("Confirm Booking")') + + // Verify success + await expect(page).toHaveURL(/\/bookings\/[a-z0-9-]+\/confirmation/) + await expect(page.locator('h1')).toContainText('Booking Confirmed') + }) +}) +``` + +### 8. Monitoring & Observability + +#### OpenTelemetry Setup +```typescript +// apps/api/src/telemetry.ts +import { NodeSDK } from '@opentelemetry/sdk-node' +import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node' +import { PeriodicExportingMetricReader, ConsoleMetricExporter } from '@opentelemetry/sdk-metrics' +import { Resource } from '@opentelemetry/resources' +import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' + +const sdk = new NodeSDK({ + resource: new Resource({ + [SemanticResourceAttributes.SERVICE_NAME]: 'harborsmith-api', + [SemanticResourceAttributes.SERVICE_VERSION]: process.env.npm_package_version, + }), + instrumentations: [ + getNodeAutoInstrumentations({ + '@opentelemetry/instrumentation-fs': { + enabled: false, + }, + }), + ], + metricReader: new PeriodicExportingMetricReader({ + exporter: new ConsoleMetricExporter(), + exportIntervalMillis: 1000, + }), +}) + +sdk.start() +``` + +#### Custom Prometheus Metrics +```typescript +// apps/api/src/metrics.ts +import { Counter, Histogram, Gauge, register } from 'prom-client' + +export const metrics = { + httpRequestDuration: new Histogram({ + name: 'http_request_duration_seconds', + help: 'Duration of HTTP requests in seconds', + labelNames: ['method', 'route', 'status'], + buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10], + }), + + bookingsCreated: new Counter({ + name: 'bookings_created_total', + help: 'Total number of bookings created', + labelNames: ['yacht_id', 'status'], + }), + + activeUsers: new Gauge({ + name: 'active_users', + help: 'Number of active users', + }), + + paymentAmount: new Histogram({ + name: 'payment_amount_usd', + help: 'Payment amounts in USD', + labelNames: ['status', 'method'], + buckets: [100, 500, 1000, 5000, 10000], + }), + + uploadedFiles: new Counter({ + name: 'uploaded_files_total', + help: 'Total number of files uploaded', + labelNames: ['type', 'size_category'], + }), +} + +// Collect default metrics +register.collectDefaultMetrics({ prefix: 'harborsmith_' }) +``` + +### 9. Error Handling & Resilience + +#### Circuit Breaker Implementation +```typescript +// packages/shared/src/circuit-breaker.ts +export class CircuitBreaker { + private failures = 0 + private lastFailureTime: number | null = null + private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED' + + constructor( + private threshold: number = 5, + private timeout: number = 60000, // 1 minute + private resetTimeout: number = 30000, // 30 seconds + ) {} + + async execute(fn: () => Promise): Promise { + if (this.state === 'OPEN') { + if (Date.now() - this.lastFailureTime! > this.timeout) { + this.state = 'HALF_OPEN' + } else { + throw new Error('Circuit breaker is OPEN') + } + } + + try { + const result = await fn() + this.onSuccess() + return result + } catch (error) { + this.onFailure() + throw error + } + } + + private onSuccess(): void { + this.failures = 0 + this.state = 'CLOSED' + } + + private onFailure(): void { + this.failures++ + this.lastFailureTime = Date.now() + + if (this.failures >= this.threshold) { + this.state = 'OPEN' + setTimeout(() => { + this.state = 'HALF_OPEN' + }, this.resetTimeout) + } + } +} +``` + +#### Retry Logic with Exponential Backoff +```typescript +// packages/shared/src/retry.ts +export async function retryWithBackoff( + fn: () => Promise, + options: RetryOptions = {}, +): Promise { + const { + maxAttempts = 3, + initialDelay = 1000, + maxDelay = 10000, + factor = 2, + jitter = true, + } = options + + let lastError: Error + + for (let attempt = 0; attempt < maxAttempts; attempt++) { + try { + return await fn() + } catch (error) { + lastError = error as Error + + if (attempt === maxAttempts - 1) { + throw lastError + } + + const delay = Math.min( + initialDelay * Math.pow(factor, attempt), + maxDelay, + ) + + const finalDelay = jitter + ? delay + Math.random() * delay * 0.1 + : delay + + await new Promise(resolve => setTimeout(resolve, finalDelay)) + } + } + + throw lastError! +} +``` + +### 10. Backup & Disaster Recovery + +#### Automated Backup Script +```bash +#!/bin/bash +# scripts/backup.sh + +set -e + +BACKUP_DIR="/backups" +TIMESTAMP=$(date +%Y%m%d_%H%M%S) +S3_BUCKET="s3://harborsmith-backups" + +# Backup PostgreSQL +echo "Backing up PostgreSQL..." +docker exec harborsmith_postgres pg_dump -U $DB_USER harborsmith | gzip > ${BACKUP_DIR}/postgres_${TIMESTAMP}.sql.gz + +# Backup MinIO data +echo "Backing up MinIO..." +docker run --rm -v minio_data:/data -v ${BACKUP_DIR}:/backup alpine tar czf /backup/minio_${TIMESTAMP}.tar.gz /data + +# Upload to S3 +echo "Uploading to S3..." +aws s3 cp ${BACKUP_DIR}/postgres_${TIMESTAMP}.sql.gz ${S3_BUCKET}/postgres/ +aws s3 cp ${BACKUP_DIR}/minio_${TIMESTAMP}.tar.gz ${S3_BUCKET}/minio/ + +# Clean up old local backups (keep last 7 days) +find ${BACKUP_DIR} -name "*.gz" -mtime +7 -delete + +# Verify backup integrity +echo "Verifying backup..." +gunzip -t ${BACKUP_DIR}/postgres_${TIMESTAMP}.sql.gz +if [ $? -eq 0 ]; then + echo "Backup successful" +else + echo "Backup verification failed" + exit 1 +fi + +# Send notification +curl -X POST $SLACK_WEBHOOK_URL \ + -H 'Content-Type: application/json' \ + -d "{\"text\":\"Backup completed successfully at ${TIMESTAMP}\"}" +``` + +#### Disaster Recovery Plan +```yaml +# infrastructure/disaster-recovery.yml +recovery_objectives: + rto: 4 hours # Recovery Time Objective + rpo: 1 hour # Recovery Point Objective + +backup_schedule: + database: + full: "0 2 * * *" # Daily at 2 AM + incremental: "0 * * * *" # Hourly + + media: + sync: "*/15 * * * *" # Every 15 minutes to S3 + +recovery_procedures: + 1_assessment: + - Identify failure scope + - Notify stakeholders + - Activate incident response team + + 2_database_recovery: + - Restore from latest backup + - Apply WAL logs for point-in-time recovery + - Verify data integrity + + 3_application_recovery: + - Deploy to backup infrastructure + - Update DNS records + - Restore service connectivity + + 4_validation: + - Run health checks + - Verify critical functionality + - Monitor for anomalies + + 5_communication: + - Update status page + - Notify customers + - Document incident + +infrastructure_redundancy: + primary_region: us-west-2 + backup_region: us-east-1 + cross_region_replication: enabled + multi_az_deployment: true +``` + +## Implementation Roadmap + +### Phase 1: Foundation (Weeks 1-4) +- [ ] Setup monorepo structure +- [ ] Configure Docker environment +- [ ] Setup PostgreSQL with Prisma +- [ ] Implement authentication with Keycloak +- [ ] Create base UI components +- [ ] Setup CI/CD pipeline + +### Phase 2: Core Features (Weeks 5-8) +- [ ] Yacht management CRUD +- [ ] Booking system +- [ ] Payment integration +- [ ] User profiles +- [ ] Search and filtering +- [ ] Media upload system + +### Phase 3: Advanced Features (Weeks 9-12) +- [ ] Real-time updates +- [ ] Video streaming +- [ ] Calendar integration +- [ ] Review system +- [ ] Analytics dashboard +- [ ] Email notifications + +### Phase 4: Polish & Launch (Weeks 13-16) +- [ ] Performance optimization +- [ ] Security audit +- [ ] Load testing +- [ ] Documentation +- [ ] Beta testing +- [ ] Production deployment + +## Conclusion + +This architecture provides a solid foundation for building a scalable, performant, and maintainable yacht charter platform. The technology choices balance modern best practices with practical considerations for rapid development and future growth. + +Key advantages of this architecture: +- **Type Safety**: End-to-end type safety with TypeScript, tRPC, and Prisma +- **Performance**: Optimized for speed with Fastify, Redis caching, and CDN delivery +- **Scalability**: Horizontal scaling ready with Docker and load balancing +- **Developer Experience**: Monorepo structure with hot reload and type checking +- **User Experience**: Beautiful UI with smooth animations and responsive design +- **Maintainability**: Clean architecture with separation of concerns + +The platform is designed to handle growth from hundreds to millions of users while maintaining excellent performance and reliability. \ No newline at end of file diff --git a/LANDING_PAGE_CONVERSION_PLAN.md b/LANDING_PAGE_CONVERSION_PLAN.md new file mode 100644 index 0000000..899df7d --- /dev/null +++ b/LANDING_PAGE_CONVERSION_PLAN.md @@ -0,0 +1,1397 @@ +# HarborSmith Landing Page Conversion Plan +## From Static HTML to Nuxt 3 + Tailwind CSS v4 + +--- + +## Table of Contents +1. [Executive Summary](#executive-summary) +2. [Current State Analysis](#current-state-analysis) +3. [Target Architecture](#target-architecture) +4. [Implementation Strategy](#implementation-strategy) +5. [Technical Implementation](#technical-implementation) +6. [Component Architecture](#component-architecture) +7. [Theme System](#theme-system) +8. [Performance Optimizations](#performance-optimizations) +9. [Deployment Strategy](#deployment-strategy) +10. [Migration Checklist](#migration-checklist) + +--- + +## Executive Summary + +This document outlines the comprehensive plan for converting the HarborSmith landing page from static HTML/CSS/JS to a production-ready Nuxt 3 application with Tailwind CSS v4, while preserving the sophisticated design system with 4 theme variants, video hero, and premium animations. + +### Key Objectives +- Preserve 100% visual parity with existing mockups +- Implement modern development practices with TypeScript +- Achieve < 1 second page load time +- Maintain SEO optimization with SSG +- Deploy as standalone Docker container + +### Approach +Hybrid system using Tailwind for layout/utilities, CSS custom properties for dynamic theming, and Vue composables for interactivity. + +--- + +## Current State Analysis + +### Existing Mockup Structure +``` +website-mockups/ +โ”œโ”€โ”€ HTML Files (18 total) +โ”‚ โ”œโ”€โ”€ index.html # Main landing page +โ”‚ โ”œโ”€โ”€ charter-booking-*.html # 4-step booking flow +โ”‚ โ”œโ”€โ”€ maintenance/*.html # Portal pages +โ”‚ โ””โ”€โ”€ portal-login.html # Authentication +โ”œโ”€โ”€ CSS Files +โ”‚ โ”œโ”€โ”€ voyage-layout.css # Main layout styles +โ”‚ โ”œโ”€โ”€ themes.css # Theme variables +โ”‚ โ”œโ”€โ”€ animations.css # Keyframe animations +โ”‚ โ””โ”€โ”€ booking.css # Booking-specific styles +โ””โ”€โ”€ JavaScript + โ”œโ”€โ”€ main.js # Core functionality + โ”œโ”€โ”€ animations.js # Scroll effects + โ””โ”€โ”€ booking.js # Booking logic +``` + +### Current Technologies +- **Styling**: Plain CSS with CSS variables +- **Themes**: 4 variants (Nautical, Coastal Dawn, Deep Sea, Monaco) +- **Icons**: Lucide via CDN +- **Fonts**: Inter + Playfair Display (Google Fonts) +- **Video**: Hero background with fallback +- **Animations**: CSS keyframes + JavaScript scroll effects + +### Design System Highlights +- Warm gradients and ocean-inspired colors +- Premium yacht aesthetic +- Smooth transitions and animations +- Mobile-responsive design +- Accessibility considerations (WCAG AA) + +--- + +## Target Architecture + +### Technology Stack +``` +Frontend Framework: Nuxt 3 (v3.10+) +CSS Framework: Tailwind CSS v4 (beta) +UI Components: Nuxt UI v3 +Animation: @vueuse/motion +State Management: Pinia +Icons: lucide-vue-next +Type Safety: TypeScript +Image Optimization: Nuxt Image +Deployment: Docker + nginx +``` + +### Project Structure +``` +harborsmith-web/ +โ”œโ”€โ”€ app.vue # Root component with theme provider +โ”œโ”€โ”€ nuxt.config.ts # Nuxt configuration +โ”œโ”€โ”€ tailwind.config.ts # Tailwind with custom properties +โ”œโ”€โ”€ package.json # Dependencies +โ”œโ”€โ”€ Dockerfile # Production build +โ”œโ”€โ”€ nginx.conf # Production server config +โ”œโ”€โ”€ components/ +โ”‚ โ”œโ”€โ”€ layout/ +โ”‚ โ”‚ โ”œโ”€โ”€ HarborNav.vue # Navigation with theme switcher +โ”‚ โ”‚ โ””โ”€โ”€ HarborFooter.vue # Footer component +โ”‚ โ”œโ”€โ”€ sections/ +โ”‚ โ”‚ โ”œโ”€โ”€ HeroVideo.vue # Video hero section +โ”‚ โ”‚ โ”œโ”€โ”€ TrustBadges.vue # Social proof +โ”‚ โ”‚ โ”œโ”€โ”€ FleetGrid.vue # Vessel showcase +โ”‚ โ”‚ โ”œโ”€โ”€ Services.vue # Services overview +โ”‚ โ”‚ โ””โ”€โ”€ Testimonials.vue # Customer reviews +โ”‚ โ””โ”€โ”€ ui/ +โ”‚ โ”œโ”€โ”€ ThemedButton.vue # Gradient CTAs +โ”‚ โ”œโ”€โ”€ VesselCard.vue # Fleet display cards +โ”‚ โ””โ”€โ”€ ThemeSwitcher.vue # Theme selector dropdown +โ”œโ”€โ”€ composables/ +โ”‚ โ”œโ”€โ”€ useTheme.ts # Theme switching logic +โ”‚ โ”œโ”€โ”€ useScrollEffects.ts # Scroll animations +โ”‚ โ””โ”€โ”€ useVideoLoader.ts # Video optimization +โ”œโ”€โ”€ stores/ +โ”‚ โ””โ”€โ”€ theme.ts # Pinia theme store +โ”œโ”€โ”€ assets/ +โ”‚ โ””โ”€โ”€ css/ +โ”‚ โ”œโ”€โ”€ base.css # Global styles +โ”‚ โ”œโ”€โ”€ themes.css # CSS custom properties +โ”‚ โ””โ”€โ”€ animations.css # Keyframe definitions +โ””โ”€โ”€ public/ + โ”œโ”€โ”€ videos/ # Hero videos + โ”œโ”€โ”€ images/ # Static images + โ””โ”€โ”€ fonts/ # Self-hosted fonts (optional) +``` + +--- + +## Implementation Strategy + +### Phase 1: Foundation Setup (Day 1) +1. Initialize Nuxt 3 project +2. Configure Tailwind CSS v4 +3. Set up TypeScript +4. Install core dependencies +5. Create base project structure + +### Phase 2: Theme System (Day 2) +1. Port CSS variables to Tailwind config +2. Implement theme switcher with Pinia +3. Create theme persistence logic +4. Test all 4 theme variants + +### Phase 3: Component Development (Days 3-4) +1. Convert navigation component +2. Build video hero section +3. Create reusable UI components +4. Implement fleet showcase grid +5. Add testimonial carousel + +### Phase 4: Animations & Polish (Day 5) +1. Implement scroll animations +2. Add hover effects +3. Optimize transitions +4. Fine-tune responsive design + +### Phase 5: Optimization & Deployment (Day 6) +1. Image optimization +2. Performance testing +3. Docker configuration +4. Production deployment + +--- + +## Technical Implementation + +### 1. Project Initialization + +```bash +# Create Nuxt 3 application +npx nuxi@latest init harborsmith-web --template minimal +cd harborsmith-web + +# Install core dependencies +npm install -D @nuxtjs/tailwindcss@next @nuxt/ui@next +npm install lucide-vue-next @vueuse/motion @vueuse/nuxt +npm install pinia @pinia/nuxt +npm install @nuxt/image + +# Development dependencies +npm install -D @types/node typescript sass +``` + +### 2. Nuxt Configuration + +```typescript +// nuxt.config.ts +export default defineNuxtConfig({ + devtools: { enabled: true }, + + modules: [ + '@nuxt/ui', + '@nuxtjs/tailwindcss', + '@vueuse/nuxt', + '@pinia/nuxt', + '@nuxt/image', + ], + + css: [ + '~/assets/css/base.css', + '~/assets/css/themes.css', + '~/assets/css/animations.css', + ], + + typescript: { + strict: true, + typeCheck: true, + }, + + nitro: { + preset: 'static', + prerender: { + routes: ['/'], + crawlLinks: true, + }, + compressPublicAssets: true, + }, + + image: { + quality: 80, + format: ['webp', 'jpg'], + screens: { + xs: 320, + sm: 640, + md: 768, + lg: 1024, + xl: 1280, + xxl: 1536, + }, + }, + + app: { + head: { + charset: 'utf-8', + viewport: 'width=device-width, initial-scale=1', + title: 'HarborSmith - Premium Yacht Charters SF Bay', + meta: [ + { name: 'description', content: 'Experience luxury yacht charters in the San Francisco Bay. Premium vessels, professional maintenance, unforgettable adventures.' }, + { property: 'og:title', content: 'HarborSmith - Your Adventure Awaits' }, + { property: 'og:image', content: '/og-image.jpg' }, + ], + link: [ + { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, + { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, + { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' }, + { + rel: 'stylesheet', + href: 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Playfair+Display:wght@400;700;900&display=swap' + }, + ], + script: [ + // Prevent theme flash + { + innerHTML: ` + (function() { + const theme = localStorage.getItem('harborsmith-theme') || 'nautical'; + document.documentElement.className = 'theme-' + theme; + })(); + `, + type: 'text/javascript', + } + ], + }, + }, + + experimental: { + payloadExtraction: false, + renderJsonPayloads: false, + viewTransition: true, + }, +}) +``` + +### 3. Tailwind Configuration + +```typescript +// tailwind.config.ts +import type { Config } from 'tailwindcss' + +export default { + content: [], + theme: { + extend: { + colors: { + // Dynamic theme colors using CSS variables + 'primary': 'rgb(var(--primary-blue) / )', + 'accent': 'rgb(var(--warm-orange) / )', + 'accent-hover': 'rgb(var(--warm-amber) / )', + 'accent-light': 'rgb(var(--warm-yellow) / )', + 'cream': 'rgb(var(--soft-cream) / )', + 'text-primary': 'rgb(var(--text-dark) / )', + 'text-secondary': 'rgb(var(--text-light) / )', + }, + backgroundImage: { + 'gradient-warm': 'var(--gradient-warm)', + 'gradient-sunset': 'var(--gradient-sunset)', + 'gradient-ocean': 'var(--gradient-ocean)', + }, + fontFamily: { + 'sans': ['Inter', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'], + 'display': ['Playfair Display', 'serif'], + }, + spacing: { + '18': '4.5rem', + '88': '22rem', + '120': '30rem', + }, + animation: { + 'fade-up': 'fadeInUp 0.8s ease forwards', + 'fade-up-delay': 'fadeInUp 0.8s ease 0.2s forwards', + 'fade-up-delay-2': 'fadeInUp 0.8s ease 0.4s forwards', + 'float': 'float 20s ease-in-out infinite', + 'pulse-slow': 'pulse 3s ease-in-out infinite', + }, + transitionTimingFunction: { + 'bounce-in': 'cubic-bezier(0.68, -0.55, 0.265, 1.55)', + }, + }, + }, + plugins: [], +} +``` + +--- + +## Component Architecture + +### Video Hero Component + +```vue + + + + +``` + +### Navigation Component + +```vue + + + + + + +``` + +--- + +## Theme System + +### CSS Custom Properties + +```css +/* assets/css/themes.css */ +:root { + /* Default Theme: Nautical */ + --primary-blue: 0 31 63; /* #001f3f */ + --warm-orange: 220 20 60; /* #dc143c */ + --warm-amber: 185 28 60; /* #b91c3c */ + --warm-yellow: 239 68 68; /* #ef4444 */ + --soft-cream: 240 244 248; /* #f0f4f8 */ + --text-dark: 10 22 40; /* #0a1628 */ + --text-light: 74 85 104; /* #4a5568 */ + + /* Gradients */ + --gradient-warm: linear-gradient(135deg, #dc143c 0%, #ef4444 100%); + --gradient-sunset: linear-gradient(135deg, #b91c3c 0%, #dc143c 50%, #ef4444 100%); + --gradient-ocean: linear-gradient(135deg, #001f3f 0%, #003366 100%); + + /* Shadows */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1); +} + +/* Coastal Dawn Theme */ +.theme-coastal-dawn { + --primary-blue: 169 180 194; /* #A9B4C2 */ + --warm-orange: 212 175 55; /* #D4AF37 */ + --warm-amber: 201 169 97; /* #C9A961 */ + --warm-yellow: 230 208 136; /* #E6D088 */ + --soft-cream: 248 247 244; /* #F8F7F4 */ + --text-dark: 51 55 69; /* #333745 */ + --text-light: 107 114 128; /* #6B7280 */ + + --gradient-warm: linear-gradient(135deg, #D4AF37 0%, #E6D088 100%); + --gradient-sunset: linear-gradient(135deg, #C9A961 0%, #D4AF37 50%, #E6D088 100%); + --gradient-ocean: linear-gradient(135deg, #A9B4C2 0%, #C5D3E0 100%); +} + +/* Deep Sea Theme */ +.theme-deep-sea { + --primary-blue: 30 32 34; /* #1E2022 */ + --warm-orange: 0 191 255; /* #00BFFF */ + --warm-amber: 30 144 255; /* #1E90FF */ + --warm-yellow: 65 105 225; /* #4169E1 */ + --soft-cream: 42 45 48; /* #2A2D30 */ + --text-dark: 229 228 226; /* #E5E4E2 */ + --text-light: 192 192 192; /* #C0C0C0 */ + + --gradient-warm: linear-gradient(135deg, #00BFFF 0%, #4169E1 100%); + --gradient-sunset: linear-gradient(135deg, #1E90FF 0%, #00BFFF 50%, #4169E1 100%); + --gradient-ocean: linear-gradient(135deg, #1E2022 0%, #2A2D30 100%); +} + +/* Monaco White Theme */ +.theme-monaco { + --primary-blue: 44 62 80; /* #2C3E50 */ + --warm-orange: 231 76 60; /* #E74C3C */ + --warm-amber: 230 126 34; /* #E67E22 */ + --warm-yellow: 243 156 18; /* #F39C12 */ + --soft-cream: 248 249 250; /* #F8F9FA */ + --text-dark: 44 62 80; /* #2C3E50 */ + --text-light: 127 140 141; /* #7F8C8D */ + + --gradient-warm: linear-gradient(135deg, #E74C3C 0%, #F39C12 100%); + --gradient-sunset: linear-gradient(135deg, #E67E22 0%, #E74C3C 50%, #F39C12 100%); + --gradient-ocean: linear-gradient(135deg, #2C3E50 0%, #34495E 100%); +} +``` + +### Theme Store (Pinia) + +```typescript +// stores/theme.ts +import { defineStore } from 'pinia' + +export type ThemeVariant = 'nautical' | 'coastal-dawn' | 'deep-sea' | 'monaco' + +interface ThemeOption { + id: ThemeVariant + name: string + description: string + colors: { + primary: string + accent: string + background: string + } +} + +export const useThemeStore = defineStore('theme', { + state: () => ({ + currentTheme: 'nautical' as ThemeVariant, + themes: [ + { + id: 'nautical', + name: 'Classic Nautical', + description: 'Deep navy and crimson', + colors: { primary: '#001f3f', accent: '#dc143c', background: '#ffffff' } + }, + { + id: 'coastal-dawn', + name: 'Coastal Dawn', + description: 'Soft blue and gold', + colors: { primary: '#A9B4C2', accent: '#D4AF37', background: '#F8F7F4' } + }, + { + id: 'deep-sea', + name: 'Deep Sea', + description: 'Dark mode with electric blue', + colors: { primary: '#1E2022', accent: '#00BFFF', background: '#2A2D30' } + }, + { + id: 'monaco', + name: 'Monaco White', + description: 'Clean and sophisticated', + colors: { primary: '#2C3E50', accent: '#E74C3C', background: '#F8F9FA' } + } + ] as ThemeOption[] + }), + + getters: { + activeTheme: (state) => state.themes.find(t => t.id === state.currentTheme), + }, + + actions: { + setTheme(theme: ThemeVariant) { + this.currentTheme = theme + + // Apply theme class to HTML element + if (process.client) { + document.documentElement.className = `theme-${theme}` + + // Persist to localStorage + localStorage.setItem('harborsmith-theme', theme) + + // Also set cookie for SSR + const cookie = useCookie('harborsmith-theme', { + httpOnly: false, + sameSite: 'strict', + maxAge: 60 * 60 * 24 * 365 // 1 year + }) + cookie.value = theme + } + }, + + loadTheme() { + if (process.client) { + const saved = localStorage.getItem('harborsmith-theme') || + useCookie('harborsmith-theme').value + + if (saved && this.themes.find(t => t.id === saved)) { + this.setTheme(saved as ThemeVariant) + } + } + } + } +}) +``` + +### Theme Switcher Component + +```vue + + + + +``` + +--- + +## Performance Optimizations + +### 1. Image Optimization Strategy + +```vue + + + + +``` + +### 2. Scroll Animation Composable + +```typescript +// composables/useScrollEffects.ts +import { useMotion } from '@vueuse/motion' +import { ref, onMounted, onUnmounted } from 'vue' + +export const useScrollEffects = () => { + const elements = ref([]) + const observer = ref() + + const fadeInUp = { + initial: { + opacity: 0, + y: 50 + }, + enter: { + opacity: 1, + y: 0, + transition: { + duration: 800, + ease: [0.4, 0, 0.2, 1] + } + } + } + + const fadeInScale = { + initial: { + opacity: 0, + scale: 0.95 + }, + enter: { + opacity: 1, + scale: 1, + transition: { + duration: 600, + ease: [0.4, 0, 0.2, 1] + } + } + } + + const initScrollAnimations = () => { + observer.value = new IntersectionObserver( + (entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('animate-visible') + observer.value?.unobserve(entry.target) + } + }) + }, + { + threshold: 0.1, + rootMargin: '50px' + } + ) + + // Find all elements with data-animate attribute + elements.value = Array.from( + document.querySelectorAll('[data-animate]') + ) as HTMLElement[] + + elements.value.forEach(el => observer.value?.observe(el)) + } + + onMounted(() => initScrollAnimations()) + onUnmounted(() => observer.value?.disconnect()) + + return { + fadeInUp, + fadeInScale + } +} +``` + +### 3. Performance Monitoring + +```typescript +// plugins/performance.client.ts +export default defineNuxtPlugin(() => { + if (process.dev) return + + // Web Vitals monitoring + onNuxtReady(() => { + // First Contentful Paint + const paintObserver = new PerformanceObserver((list) => { + for (const entry of list.getEntries()) { + if (entry.name === 'first-contentful-paint') { + console.log('FCP:', entry.startTime) + // Send to analytics + } + } + }) + paintObserver.observe({ entryTypes: ['paint'] }) + + // Largest Contentful Paint + const lcpObserver = new PerformanceObserver((list) => { + const entries = list.getEntries() + const lastEntry = entries[entries.length - 1] + console.log('LCP:', lastEntry.startTime) + // Send to analytics + }) + lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] }) + + // Cumulative Layout Shift + let clsValue = 0 + const clsObserver = new PerformanceObserver((list) => { + for (const entry of list.getEntries()) { + if (!entry.hadRecentInput) { + clsValue += entry.value + console.log('CLS:', clsValue) + } + } + }) + clsObserver.observe({ entryTypes: ['layout-shift'] }) + }) +}) +``` + +--- + +## Deployment Strategy + +### Docker Configuration + +```dockerfile +# Dockerfile +# Build stage +FROM node:20-alpine AS builder + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci --only=production + +# Copy source code +COPY . . + +# Build application +RUN npm run generate + +# Production stage +FROM nginx:alpine + +# Install nginx-module-brotli for better compression +RUN apk add --no-cache nginx-mod-http-brotli + +# Copy built static files +COPY --from=builder /app/.output/public /usr/share/nginx/html + +# Copy nginx configuration +COPY nginx.conf /etc/nginx/nginx.conf + +# Copy custom nginx site config +COPY default.conf /etc/nginx/conf.d/default.conf + +# Expose port +EXPOSE 80 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] +``` + +### nginx Configuration + +```nginx +# nginx.conf +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + access_log /var/log/nginx/access.log main; + + # Performance + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip compression + gzip on; + gzip_disable "msie6"; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml text/javascript + application/json application/javascript application/xml+rss + application/rss+xml application/atom+xml image/svg+xml + text/x-js text/x-cross-domain-policy application/x-font-ttf + application/x-font-opentype application/vnd.ms-fontobject + image/x-icon; + + # Brotli compression + brotli on; + brotli_comp_level 6; + brotli_types text/plain text/css text/xml text/javascript + application/json application/javascript application/xml+rss + application/rss+xml application/atom+xml image/svg+xml; + + include /etc/nginx/conf.d/*.conf; +} +``` + +```nginx +# default.conf +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' https:; script-src 'self' 'unsafe-inline' https://unpkg.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; media-src 'self' https://videos.pexels.com;" always; + + # Cache static assets + location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg|webp)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Cache HTML (shorter duration) + location ~* \.(html)$ { + expires 1h; + add_header Cache-Control "public, must-revalidate"; + } + + # SPA fallback + location / { + try_files $uri $uri/ /index.html; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "OK"; + } +} +``` + +### Docker Compose (Development) + +```yaml +# docker-compose.yml +version: '3.8' + +services: + web: + build: + context: . + dockerfile: Dockerfile + container_name: harborsmith-web + ports: + - "3001:80" + environment: + - NODE_ENV=production + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - harborsmith + +networks: + harborsmith: + driver: bridge +``` + +--- + +## Migration Checklist + +### Pre-Migration +- [ ] Backup current mockups +- [ ] Document all custom styles +- [ ] List all JavaScript functionality +- [ ] Catalog all images and videos +- [ ] Note responsive breakpoints + +### Phase 1: Setup +- [ ] Initialize Nuxt 3 project +- [ ] Configure TypeScript +- [ ] Install dependencies +- [ ] Setup Tailwind CSS v4 +- [ ] Configure Nuxt modules + +### Phase 2: Theme System +- [ ] Port CSS variables +- [ ] Create theme files +- [ ] Implement Pinia store +- [ ] Build theme switcher +- [ ] Test theme persistence + +### Phase 3: Components +- [ ] Convert navigation +- [ ] Build video hero +- [ ] Create button components +- [ ] Port trust badges +- [ ] Convert fleet grid +- [ ] Build testimonials +- [ ] Add footer + +### Phase 4: Content +- [ ] Migrate images +- [ ] Optimize videos +- [ ] Add meta tags +- [ ] Setup analytics +- [ ] Configure sitemap + +### Phase 5: Optimization +- [ ] Run Lighthouse audit +- [ ] Optimize bundle size +- [ ] Test all viewports +- [ ] Check accessibility +- [ ] Validate SEO + +### Phase 6: Deployment +- [ ] Build Docker image +- [ ] Configure nginx +- [ ] Setup CI/CD +- [ ] Deploy to staging +- [ ] Production deployment + +### Post-Migration +- [ ] Performance monitoring +- [ ] Error tracking +- [ ] User testing +- [ ] Documentation +- [ ] Team training + +--- + +## Testing Strategy + +### Unit Tests (Vitest) + +```typescript +// components/ui/ThemedButton.test.ts +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import ThemedButton from './ThemedButton.vue' + +describe('ThemedButton', () => { + it('renders with primary variant', () => { + const wrapper = mount(ThemedButton, { + props: { + variant: 'primary' + } + }) + + expect(wrapper.classes()).toContain('bg-gradient-warm') + }) + + it('emits click event', async () => { + const wrapper = mount(ThemedButton) + await wrapper.trigger('click') + + expect(wrapper.emitted('click')).toHaveLength(1) + }) +}) +``` + +### E2E Tests (Playwright) + +```typescript +// tests/e2e/landing.spec.ts +import { test, expect } from '@playwright/test' + +test.describe('Landing Page', () => { + test('loads with video hero', async ({ page }) => { + await page.goto('/') + + // Check hero section + const hero = await page.locator('.hero-voyage') + await expect(hero).toBeVisible() + + // Check video or fallback image + const video = page.locator('video') + const hasVideo = await video.count() > 0 + + if (hasVideo) { + await expect(video).toHaveAttribute('autoplay') + } else { + const fallbackImage = page.locator('.hero-voyage img') + await expect(fallbackImage).toBeVisible() + } + }) + + test('theme switcher works', async ({ page }) => { + await page.goto('/') + + // Open theme menu + await page.click('[data-testid="theme-switcher"]') + + // Select coastal dawn theme + await page.click('text=Coastal Dawn') + + // Check theme applied + const html = page.locator('html') + await expect(html).toHaveClass('theme-coastal-dawn') + + // Check persistence + await page.reload() + await expect(html).toHaveClass('theme-coastal-dawn') + }) +}) +``` + +--- + +## Success Metrics + +### Performance Targets +- **Lighthouse Score**: > 95 (all categories) +- **First Contentful Paint**: < 1.2s +- **Largest Contentful Paint**: < 2.5s +- **Time to Interactive**: < 3.5s +- **Cumulative Layout Shift**: < 0.1 +- **JavaScript Bundle**: < 200KB (initial) +- **CSS Bundle**: < 50KB + +### Quality Metrics +- **TypeScript Coverage**: 100% +- **Test Coverage**: > 80% +- **Accessibility**: WCAG AA compliant +- **SEO Score**: 100 +- **Mobile Score**: 100 + +### Business Metrics +- **Conversion Rate**: Track booking CTAs +- **Engagement**: Time on site, scroll depth +- **Theme Usage**: Analytics on theme preferences +- **Performance**: Real user monitoring + +--- + +## Maintenance & Updates + +### Regular Tasks +- Update dependencies monthly +- Review performance metrics weekly +- Monitor error logs daily +- Backup before major changes +- Document all customizations + +### Optimization Opportunities +- Implement service worker for offline +- Add PWA capabilities +- Integrate with CDN +- Implement A/B testing +- Add personalization features + +--- + +## Conclusion + +This comprehensive plan provides a clear roadmap for converting the HarborSmith landing page from static HTML to a modern Nuxt 3 application. The approach preserves the sophisticated design while implementing best practices for performance, maintainability, and developer experience. + +The hybrid strategy of using Tailwind for utilities with CSS custom properties for theming ensures flexibility while maintaining the exact visual design. The component-based architecture with TypeScript provides type safety and reusability for future development. + +Following this plan will result in a production-ready landing page that loads in under 1 second, maintains perfect visual parity with the mockups, and provides a solid foundation for the entire HarborSmith web platform. \ No newline at end of file diff --git a/NAUTICAL_DESIGN_SYSTEM.md b/NAUTICAL_DESIGN_SYSTEM.md new file mode 100644 index 0000000..fde7282 --- /dev/null +++ b/NAUTICAL_DESIGN_SYSTEM.md @@ -0,0 +1,402 @@ +# HarborSmith Nautical Theme Design System + +## Brand Identity +HarborSmith embodies premium maritime elegance with a sophisticated nautical aesthetic. The design language draws inspiration from classic yacht clubs, luxury vessels, and the deep blue waters of the San Francisco Bay. + +## Color System + +### Primary Palette +```css +/* Core Brand Colors */ +--navy-blue: #001f3f; /* RGB: 0, 31, 63 - Deep maritime authority */ +--crimson: #dc143c; /* RGB: 220, 20, 60 - Premium accent, CTAs */ +--warm-amber: #b91c3c; /* RGB: 185, 28, 60 - Interactive hover states */ +--warm-yellow: #ef4444; /* RGB: 239, 68, 68 - Highlights, badges */ +``` + +### Neutral Colors +```css +/* Background & Surface Colors */ +--soft-cream: #f0f4f8; /* RGB: 240, 244, 248 - Light backgrounds */ +--white: #ffffff; /* RGB: 255, 255, 255 - Primary backgrounds */ +--gray-50: #f9fafb; /* RGB: 249, 250, 251 - Subtle backgrounds */ +--gray-100: #f3f4f6; /* RGB: 243, 244, 246 - Borders */ +--gray-200: #e5e7eb; /* RGB: 229, 231, 235 - Dividers */ +``` + +### Text Colors +```css +/* Typography Colors */ +--text-dark: #0a1628; /* RGB: 10, 22, 40 - Primary text */ +--text-light: #4a5568; /* RGB: 74, 85, 104 - Secondary text */ +--text-muted: #6b7280; /* RGB: 107, 114, 128 - Muted text */ +--text-white: #ffffff; /* RGB: 255, 255, 255 - Inverse text */ +``` + +### Gradient System +```css +/* Brand Gradients */ +--gradient-warm: linear-gradient(135deg, #dc143c 0%, #ef4444 100%); +--gradient-sunset: linear-gradient(135deg, #b91c3c 0%, #dc143c 50%, #ef4444 100%); +--gradient-ocean: linear-gradient(135deg, #001f3f 0%, #003366 100%); +--gradient-overlay: linear-gradient(180deg, rgba(0,31,63,0.8) 0%, transparent 100%); +``` + +## Typography + +### Font Families +```css +--font-display: 'Playfair Display', serif; /* Elegant headers */ +--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; /* Clean body text */ +``` + +### Type Scale +| Level | Size | Line Height | Font Weight | Usage | +|-------|------|-------------|-------------|-------| +| Display | 72px | 1.1 | 700 | Hero headlines | +| H1 | 56px | 1.2 | 700 | Page titles | +| H2 | 48px | 1.25 | 600 | Section headers | +| H3 | 36px | 1.3 | 600 | Subsections | +| H4 | 24px | 1.35 | 500 | Card titles | +| H5 | 20px | 1.4 | 500 | Subheadings | +| Body Large | 20px | 1.6 | 400 | Lead paragraphs | +| Body | 16px | 1.5 | 400 | Default text | +| Small | 14px | 1.4 | 400 | Captions, meta | +| XSmall | 12px | 1.3 | 400 | Labels, badges | + +### Font Weights +- Light: 300 (Subtle text) +- Regular: 400 (Body content) +- Medium: 500 (Emphasis) +- Semibold: 600 (Headers) +- Bold: 700 (Strong emphasis) +- Black: 900 (Display only) + +## Spacing System + +### Base Unit: 4px +```css +--space-0: 0px; +--space-1: 4px; +--space-2: 8px; +--space-3: 12px; +--space-4: 16px; +--space-5: 20px; +--space-6: 24px; +--space-8: 32px; +--space-10: 40px; +--space-12: 48px; +--space-16: 64px; +--space-20: 80px; +--space-24: 96px; +--space-32: 128px; +``` + +### Component Spacing +- **Inline elements**: 8px (space-2) +- **Related elements**: 16px (space-4) +- **Section spacing**: 48px mobile, 80px desktop +- **Page padding**: 16px mobile, 32px tablet, 48px desktop + +## Layout System + +### Container Widths +```css +--container-xs: 480px; /* Mobile */ +--container-sm: 640px; /* Small tablet */ +--container-md: 768px; /* Tablet */ +--container-lg: 1024px; /* Desktop */ +--container-xl: 1280px; /* Wide desktop */ +--container-2xl: 1536px; /* Ultra wide */ +``` + +### Breakpoints +```css +/* Mobile First Approach */ +@media (min-width: 640px) { /* sm */ } +@media (min-width: 768px) { /* md */ } +@media (min-width: 1024px) { /* lg */ } +@media (min-width: 1280px) { /* xl */ } +@media (min-width: 1536px) { /* 2xl */ } +``` + +### Grid System +- **Columns**: 12-column grid +- **Gap**: 16px mobile, 24px tablet, 32px desktop +- **Margins**: 16px mobile, 32px desktop + +## Border System + +### Border Radius +```css +--radius-none: 0px; +--radius-sm: 4px; /* Inputs, small elements */ +--radius-md: 8px; /* Cards, buttons */ +--radius-lg: 16px; /* Modals, large cards */ +--radius-xl: 24px; /* Hero sections */ +--radius-full: 9999px; /* Pills, avatars */ +``` + +### Border Widths +```css +--border-0: 0px; +--border: 1px; +--border-2: 2px; +--border-4: 4px; +``` + +## Shadow System + +### Elevation Shadows +```css +--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); +--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); +--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); +--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1); +--shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25); +``` + +### Color Shadows +```css +--shadow-warm: 0 10px 30px rgba(220, 20, 60, 0.3); +--shadow-ocean: 0 10px 30px rgba(0, 31, 63, 0.2); +--shadow-soft: 0 10px 40px rgba(0, 0, 0, 0.08); +``` + +## Animation System + +### Timing +```css +--duration-instant: 100ms; +--duration-fast: 200ms; +--duration-normal: 300ms; +--duration-slow: 500ms; +--duration-slower: 800ms; +``` + +### Easing Functions +```css +--ease-linear: linear; +--ease-in: cubic-bezier(0.4, 0, 1, 1); +--ease-out: cubic-bezier(0, 0, 0.2, 1); +--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); +--ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55); +``` + +### Standard Transitions +```css +/* Default transition for all interactive elements */ +transition: all var(--duration-normal) var(--ease-in-out); + +/* Hover effects */ +transform: translateY(-2px); +box-shadow: var(--shadow-lg); + +/* Active/pressed state */ +transform: scale(0.98); +``` + +## Component Patterns + +### Buttons + +#### Primary Button +```css +background: var(--gradient-warm); +color: white; +padding: 12px 24px; +border-radius: var(--radius-md); +font-weight: 600; +transition: all var(--duration-normal); +box-shadow: var(--shadow-warm); +``` + +#### Secondary Button +```css +background: transparent; +color: var(--navy-blue); +border: 2px solid var(--navy-blue); +padding: 10px 22px; +border-radius: var(--radius-md); +``` + +#### Button States +- **Hover**: Elevated shadow, scale(1.02) +- **Active**: scale(0.98) +- **Disabled**: opacity(0.5), cursor(not-allowed) + +### Cards + +#### Base Card +```css +background: white; +border-radius: var(--radius-md); +padding: var(--space-6); +box-shadow: var(--shadow-md); +transition: all var(--duration-normal); +``` + +#### Card Hover +```css +transform: translateY(-4px); +box-shadow: var(--shadow-xl); +``` + +### Form Elements + +#### Input Fields +```css +background: white; +border: 1px solid var(--gray-200); +border-radius: var(--radius-sm); +padding: 10px 16px; +font-size: 16px; +transition: all var(--duration-fast); +``` + +#### Input Focus +```css +border-color: var(--crimson); +box-shadow: 0 0 0 3px rgba(220, 20, 60, 0.1); +outline: none; +``` + +### Navigation + +#### Desktop Nav +- Transparent on hero sections +- White background on scroll +- Backdrop blur effect +- Smooth transitions + +#### Mobile Nav +- Slide-down animation +- Full-width menu +- Touch-friendly targets (min 44px) + +## Icons & Imagery + +### Icon System +- **Library**: Lucide Icons +- **Size Scale**: 16px, 20px, 24px, 32px +- **Stroke Width**: 1.5px default, 2px for emphasis +- **Color**: Inherit from parent + +### Image Treatment +- **Border Radius**: var(--radius-md) for cards +- **Aspect Ratios**: 16:9 (hero), 4:3 (cards), 1:1 (avatars) +- **Loading**: Blur-up technique with LQIP +- **Overlays**: Gradient overlays for text legibility + +## Responsive Design + +### Mobile First Principles +1. Start with mobile layout +2. Enhance for larger screens +3. Touch targets minimum 44px +4. Readable font sizes (min 14px) +5. Appropriate spacing for touch + +### Breakpoint Behaviors +- **Mobile (< 640px)**: Single column, stacked layout +- **Tablet (640px - 1024px)**: 2-column grids, side navigation +- **Desktop (> 1024px)**: Multi-column, full features + +## Accessibility + +### Color Contrast +- Normal text: 4.5:1 minimum +- Large text: 3:1 minimum +- Interactive elements: 3:1 minimum +- Focus indicators: Visible outline + +### Interactive Elements +- Keyboard navigable +- Focus indicators +- ARIA labels +- Semantic HTML +- Skip navigation links + +## Performance Guidelines + +### Image Optimization +- WebP format with JPEG fallback +- Responsive images with srcset +- Lazy loading below fold +- Blur-up placeholders + +### CSS Optimization +- Critical CSS inline +- Unused CSS removed +- CSS custom properties for theming +- Minimal specificity + +### Animation Performance +- Prefer transform and opacity +- Use will-change sparingly +- 60fps target +- Reduce motion preference support + +## Implementation Notes + +### CSS Architecture +- Utility-first with Tailwind CSS v4 +- Component classes for complex patterns +- CSS custom properties for dynamic values +- Scoped styles in Vue components + +### Component Structure +```vue + + + + + +``` + +### Naming Conventions +- **Components**: PascalCase (HeroVideo.vue) +- **Composables**: camelCase with 'use' prefix (useTheme.ts) +- **CSS Classes**: kebab-case (nav-link) +- **CSS Variables**: kebab-case with prefix (--space-4) + +## Usage Examples + +### Button Component +```vue + + Book Charter + + +``` + +### Card Component +```vue + +``` + +### Typography +```vue +

+ Your Adventure Awaits +

+

+ Experience luxury yacht charters in the San Francisco Bay +

+``` + +--- + +This design system ensures consistency, accessibility, and performance across the HarborSmith platform while maintaining the premium nautical aesthetic that defines the brand. \ No newline at end of file diff --git a/apps/website/.gitignore b/apps/website/.gitignore new file mode 100644 index 0000000..4a7f73a --- /dev/null +++ b/apps/website/.gitignore @@ -0,0 +1,24 @@ +# Nuxt dev/build outputs +.output +.data +.nuxt +.nitro +.cache +dist + +# Node dependencies +node_modules + +# Logs +logs +*.log + +# Misc +.DS_Store +.fleet +.idea + +# Local env files +.env +.env.* +!.env.example diff --git a/apps/website/Dockerfile b/apps/website/Dockerfile new file mode 100644 index 0000000..5980282 --- /dev/null +++ b/apps/website/Dockerfile @@ -0,0 +1,38 @@ +# Multi-stage build for Harbor Smith website + +# Stage 1: Build +FROM node:20-alpine AS builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci --only=production && \ + npm cache clean --force + +# Copy source code +COPY . . + +# Build the Nuxt application for static generation +RUN npm run generate + +# Stage 2: Production +FROM nginx:alpine + +# Copy custom nginx config +COPY nginx.conf /etc/nginx/nginx.conf + +# Copy built static files from builder stage +COPY --from=builder /app/.output/public /usr/share/nginx/html + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost || exit 1 + +# Expose port 80 +EXPOSE 80 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/apps/website/README.md b/apps/website/README.md new file mode 100644 index 0000000..25b5821 --- /dev/null +++ b/apps/website/README.md @@ -0,0 +1,75 @@ +# Nuxt Minimal Starter + +Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. + +## Setup + +Make sure to install dependencies: + +```bash +# npm +npm install + +# pnpm +pnpm install + +# yarn +yarn install + +# bun +bun install +``` + +## Development Server + +Start the development server on `http://localhost:3000`: + +```bash +# npm +npm run dev + +# pnpm +pnpm dev + +# yarn +yarn dev + +# bun +bun run dev +``` + +## Production + +Build the application for production: + +```bash +# npm +npm run build + +# pnpm +pnpm build + +# yarn +yarn build + +# bun +bun run build +``` + +Locally preview production build: + +```bash +# npm +npm run preview + +# pnpm +pnpm preview + +# yarn +yarn preview + +# bun +bun run preview +``` + +Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. diff --git a/apps/website/assets/css/main.css b/apps/website/assets/css/main.css new file mode 100644 index 0000000..948353c --- /dev/null +++ b/apps/website/assets/css/main.css @@ -0,0 +1,371 @@ +/* Harbor Smith Main Styles */ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* Smooth Scroll Behavior */ +html { + scroll-behavior: smooth; +} + +/* Accessibility: Reduced Motion */ +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } + + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } + + .hero-video-container, + .parallax { + transform: none !important; + } +} + +/* Custom Properties */ +@layer base { + :root { + /* Primary Colors */ + --primary-blue: #001f3f; + --harbor-navy: #1e3a5f; + --harbor-gold: #b48b4e; + --harbor-amber: #9d7943; + --harbor-yellow: #c9a56f; + --harbor-light: #f0f0f0; + + /* Gradients */ + --gradient-warm: linear-gradient(135deg, #b48b4e 0%, #c9a56f 100%); + --gradient-blue: linear-gradient(135deg, #001f3f 0%, #1e3a5f 100%); + } +} + +/* Typography */ +@layer base { + body { + @apply font-sans text-gray-800 antialiased; + } + + h1, h2, h3, h4, h5, h6 { + @apply font-serif; + } + + h1 { + @apply text-5xl md:text-6xl lg:text-7xl font-bold; + } + + h2 { + @apply text-4xl md:text-5xl font-bold; + } + + h3 { + @apply text-3xl md:text-4xl font-bold; + } + + h4 { + @apply text-2xl md:text-3xl font-semibold; + } + + h5 { + @apply text-xl md:text-2xl font-semibold; + } + + h6 { + @apply text-lg md:text-xl font-semibold; + } +} + +/* Page Transitions */ +.page-enter-active, +.page-leave-active { + transition: all 0.4s; +} +.page-enter-from { + opacity: 0; + transform: translateY(20px); +} +.page-leave-to { + opacity: 0; + transform: translateY(-20px); +} + +.layout-enter-active, +.layout-leave-active { + transition: all 0.4s; +} +.layout-enter-from { + opacity: 0; + transform: scale(0.98); +} +.layout-leave-to { + opacity: 0; + transform: scale(1.02); +} + +/* Harbor Smith Custom Components */ +@layer components { + /* Navigation Styles handled via voyage-layout.css */ + /* Button Styles */ + .btn-primary-warm { + @apply px-8 py-3 bg-gradient-to-r from-harbor-gold to-harbor-yellow text-white font-semibold rounded-full; + @apply hover:shadow-xl transform hover:-translate-y-0.5 transition-all duration-300; + @apply relative overflow-hidden; + } + + .btn-secondary-warm { + @apply px-8 py-3 bg-transparent border-2 border-harbor-gold text-harbor-gold font-semibold rounded-full; + @apply hover:bg-harbor-gold hover:text-white transition-all duration-300; + } + + .btn-booking { + @apply inline-flex items-center justify-center px-6 py-3 rounded-lg font-semibold; + @apply transition-all duration-300 transform hover:-translate-y-1; + } + + .btn-booking.primary { + @apply bg-gradient-to-r from-harbor-gold to-harbor-yellow text-white; + @apply hover:shadow-2xl hover:from-harbor-amber hover:to-harbor-gold; + } + + .btn-booking.secondary { + @apply bg-white text-harbor-navy border-2 border-harbor-gold; + @apply hover:bg-harbor-gold hover:text-white hover:border-harbor-gold; + } + + /* Card Styles */ + .story-card { + @apply bg-white rounded-xl overflow-hidden shadow-lg; + @apply transform transition-all duration-500 hover:-translate-y-2 hover:shadow-2xl; + } + + .booking-card { + @apply bg-white rounded-2xl p-8 shadow-xl; + @apply transform transition-all duration-500; + } + + .booking-card.featured { + @apply scale-105 border-4 border-harbor-gold; + box-shadow: 0 20px 40px rgba(180, 139, 78, 0.2); + } + + .yacht-card { + @apply bg-white rounded-2xl overflow-hidden shadow-xl; + @apply opacity-0 scale-95 transition-all duration-700; + } + + .yacht-card.active { + @apply opacity-100 scale-100; + } + + /* Hero Section */ + .hero-video-container { + @apply absolute inset-0 w-full h-full overflow-hidden; + } + + .hero-overlay { + @apply absolute inset-0 bg-gradient-to-b from-black/40 via-black/20 to-black/40; + } + + .gradient-warm { + background: linear-gradient(135deg, rgba(180, 139, 78, 0.2) 0%, rgba(201, 165, 111, 0.1) 100%); + } + + .gradient-depth { + background: linear-gradient(to top, rgba(30, 58, 95, 0.3) 0%, transparent 100%); + } + + .hero-content { + @apply relative z-10 text-white text-center; + } + + /* Fleet Carousel */ + .fleet-nav { + @apply absolute top-1/2 -translate-y-1/2 z-10; + @apply w-12 h-12 bg-white/90 rounded-full shadow-xl; + @apply flex items-center justify-center cursor-pointer; + @apply hover:bg-white hover:scale-110 transition-all duration-300; + } + + .fleet-prev { + @apply fleet-nav left-4; + } + + .fleet-next { + @apply fleet-nav right-4; + } + + .fleet-dots { + @apply flex gap-3 justify-center mt-8; + } + + .dot { + @apply w-3 h-3 rounded-full bg-gray-300 cursor-pointer; + @apply transition-all duration-300 hover:bg-harbor-gold; + } + + .dot.active { + @apply bg-harbor-gold w-8; + } + + /* Trust Badge */ + .trust-badge { + @apply inline-flex items-center gap-2 px-4 py-2 bg-harbor-gold/20 backdrop-blur-sm rounded-full; + } + + /* Section Styles */ + .section-title { + @apply text-4xl md:text-5xl lg:text-6xl font-serif font-bold text-harbor-navy; + @apply mb-4; + } + + .section-subtitle { + @apply text-xl md:text-2xl text-gray-600 mb-12; + } +} + +/* Animation Utilities */ +@layer utilities { + .animate-fade-in { + animation: fadeIn 1s ease-out forwards; + } + + .animate-fade-up-delay { + animation: fadeUp 1s ease-out 0.3s both; + } + + .animate-fade-up-delay-2 { + animation: fadeUp 1s ease-out 0.6s both; + } + + .animate-slide-up { + animation: slideUp 0.6s ease-out forwards; + } + + .animate-scale-in { + animation: scaleIn 0.5s ease-out forwards; + } + + .animate-wave { + animation: wave 10s ease-in-out infinite; + } + + @keyframes fadeUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + @keyframes wave { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-20px); } + } +} + +/* Scrollbar Styles */ +::-webkit-scrollbar { + width: 12px; + height: 12px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; +} + +::-webkit-scrollbar-thumb { + background: var(--harbor-gold); + border-radius: 6px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--harbor-amber); +} + +/* Ripple Effect for Buttons */ +@keyframes ripple { + to { + transform: scale(4); + opacity: 0; + } +} + +.ripple { + position: absolute; + border-radius: 50%; + background: rgba(255, 255, 255, 0.6); + transform: scale(0); + animation: ripple 0.6s ease-out; + pointer-events: none; + will-change: transform, opacity; +} + +/* Gold Drop Shadows */ +.shadow-gold { + box-shadow: 0 4px 15px rgba(180, 139, 78, 0.2); +} + +.shadow-gold-lg { + box-shadow: 0 10px 25px rgba(180, 139, 78, 0.25); +} + +.shadow-gold-xl { + box-shadow: 0 20px 40px rgba(180, 139, 78, 0.3); +} + +/* Apply gold shadows to key elements */ +.btn-primary-warm, +.btn-secondary-warm { + box-shadow: 0 4px 15px rgba(180, 139, 78, 0.2); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.btn-primary-warm:hover, +.btn-secondary-warm:hover { + box-shadow: 0 8px 25px rgba(180, 139, 78, 0.35); +} + +.story-card, +.booking-card, +.service-card { + transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); +} + +.story-card:hover, +.booking-card:hover, +.service-card:hover { + box-shadow: 0 20px 40px rgba(180, 139, 78, 0.25); +} + +/* Video Background Optimization */ +.hero-video { + @apply absolute w-full h-full object-cover; + transform: scale(1.1); +} + +/* Loading States */ +.skeleton { + @apply bg-gray-200 animate-pulse rounded; +} + +/* Responsive Typography Scale */ +@media (max-width: 768px) { + html { + font-size: 14px; + } +} + +@media (min-width: 1536px) { + html { + font-size: 18px; + } +} diff --git a/apps/website/assets/css/themes.css b/apps/website/assets/css/themes.css new file mode 100644 index 0000000..9a7edeb --- /dev/null +++ b/apps/website/assets/css/themes.css @@ -0,0 +1,174 @@ +/* HarborSmith - Theme Styles */ +/* ========================= */ + +/* Classical Nautical Theme (Default) - Navy & Crimson */ +[data-theme="nautical"] { + --primary: #001f3f; /* Classic navy blue */ + --accent: #dc143c; /* Nautical red/crimson */ + --background: #ffffff; + --surface: #f0f4f8; + --text: #0a1628; + --text-secondary: #4a5568; + --border: #cbd5e0; + --overlay: rgba(0, 31, 63, 0.7); + --gradient-start: #001f3f; + --gradient-end: #003366; +} + +/* Gold Theme - Navy with Gold accents */ +[data-theme="gold"] { + --primary: #001f3f; /* Classic navy blue */ + --accent: #bc970c; /* Gold */ + --background: #ffffff; + --surface: #f0f4f8; + --text: #0a1628; + --text-secondary: #4a5568; + --border: #cbd5e0; + --overlay: rgba(0, 31, 63, 0.7); + --gradient-start: #001f3f; + --gradient-end: #003366; +} + +/* Coastal Dawn Theme - Soft & Serene Luxury */ +[data-theme="coastal-dawn"] { + --primary: #A9B4C2; /* Cadet Blue */ + --accent: #D4AF37; /* Gilded Gold */ + --background: #F8F7F4; /* Alabaster White */ + --surface: #FFFFFF; + --text: #333745; /* Charcoal Slate */ + --text-secondary: #6B7280; + --border: #E5E7EB; + --overlay: rgba(169, 180, 194, 0.4); + --gradient-start: #A9B4C2; + --gradient-end: #C5D3E0; +} + +/* Deep Sea Slate Theme - Modern & Technical */ +[data-theme="deep-sea"] { + --primary: #1E2022; /* Gunmetal Grey */ + --accent: #00BFFF; /* Deep Sky Blue */ + --background: #1E2022; /* Dark background */ + --surface: #2A2D30; + --text: #E5E4E2; /* Platinum text */ + --text-secondary: #C0C0C0; /* Silver */ + --border: #3A3D40; + --overlay: rgba(30, 32, 34, 0.8); + --gradient-start: #1E2022; + --gradient-end: #2A2D30; +} + +/* Monaco White Theme - Pristine & Minimalist */ +[data-theme="monaco-white"] { + --primary: #2C3E50; /* Midnight Blue */ + --accent: #E74C3C; /* Pomegranate Red */ + --background: #FFFFFF; + --surface: #F8F9FA; + --text: #2C3E50; + --text-secondary: #7F8C8D; + --border: #ECF0F1; + --overlay: rgba(44, 62, 80, 0.05); + --gradient-start: #FFFFFF; + --gradient-end: #F8F9FA; +} + +/* Update hero gradient for all themes */ +[data-theme] .hero-background { + background: linear-gradient(135deg, var(--gradient-start) 0%, var(--gradient-end) 100%); +} + +/* Ensure good contrast for nav items */ +[data-theme] .nav-link { + color: var(--text); + font-weight: 500; +} + +[data-theme] .nav-link:hover { + color: var(--accent); +} + +/* Theme switcher button styling */ +[data-theme] .theme-switcher { + background: var(--surface); + border: 2px solid var(--border); + color: var(--text); +} + +[data-theme] .theme-switcher:hover { + background: var(--accent); + color: white; + border-color: var(--accent); +} + +/* Better button contrast */ +[data-theme] .btn-primary { + background: var(--accent); + color: white; + border: 2px solid var(--accent); +} + +[data-theme] .btn-primary:hover { + background: var(--primary); + border-color: var(--primary); +} + +/* Schedule Service button - make it filled */ +[data-theme] .btn-secondary { + background: var(--primary); + color: white; + border: 2px solid var(--primary); +} + +[data-theme] .btn-secondary:hover { + background: var(--accent); + border-color: var(--accent); + color: white; +} + +/* Card improvements */ +[data-theme] .service-card { + background: var(--surface); + border: 1px solid var(--border); +} + +[data-theme] .yacht-card { + background: white; + border: 1px solid var(--border); +} + +/* Stats section contrast */ +[data-theme] .stats-section { + background: var(--surface); +} + +/* Footer styling */ +[data-theme] footer { + background: var(--primary); + color: white; +} + +[data-theme] footer a { + color: rgba(255, 255, 255, 0.8); +} + +[data-theme] footer a:hover { + color: var(--accent); +} + +/* Special styling for Classical Nautical theme */ +[data-theme="nautical"] .btn-primary { + background: var(--accent); + box-shadow: 0 4px 6px rgba(220, 20, 60, 0.2); +} + +[data-theme="nautical"] .btn-secondary { + background: var(--primary); + box-shadow: 0 4px 6px rgba(0, 31, 63, 0.2); +} + +[data-theme="nautical"] .service-card { + border-color: var(--primary); +} + +[data-theme="nautical"] .yacht-card { + border-top: 3px solid var(--accent); +} \ No newline at end of file diff --git a/apps/website/assets/css/voyage-layout.css b/apps/website/assets/css/voyage-layout.css new file mode 100644 index 0000000..f3b14ef --- /dev/null +++ b/apps/website/assets/css/voyage-layout.css @@ -0,0 +1,1659 @@ +/* Voyage Layout - Warm, Cinematic, Inviting */ + +:root { + /* Default Theme - Navy & Gold */ + --primary-blue: #001f3f; + --warm-orange: #b48b4e; + --warm-amber: #9d7943; + --warm-yellow: #c9a56f; + --soft-cream: #f0f4f8; + --text-dark: #0a1628; + --text-light: #4a5568; + --white: #ffffff; + --bg-light: #ffffff; + --border: #cbd5e0; + + /* Gradients */ + --gradient-warm: linear-gradient(135deg, #b48b4e 0%, #c9a56f 100%); + --gradient-sunset: linear-gradient(135deg, #9d7943 0%, #b48b4e 50%, #c9a56f 100%); + --gradient-ocean: linear-gradient(135deg, #001f3f 0%, #003366 100%); + + /* Spacing */ + --space-xs: 0.5rem; + --space-sm: 1rem; + --space-md: 2rem; + --space-lg: 3rem; + --space-xl: 4rem; + --space-2xl: 6rem; + + /* Typography */ + --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + --font-display: 'Playfair Display', serif; + + /* Transitions */ + --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); +} + + +/* Coastal Dawn Theme - Soft, serene, golden accents */ +body.theme-coastal-dawn { + --primary-blue: #A9B4C2; + --warm-orange: #D4AF37; + --warm-amber: #C9A961; + --warm-yellow: #E6D088; + --soft-cream: #F8F7F4; + --text-dark: #333745; + --text-light: #6B7280; + --white: #FFFFFF; + --bg-light: #F8F7F4; + --border: #E5E7EB; + --gradient-warm: linear-gradient(135deg, #D4AF37 0%, #E6D088 100%); + --gradient-sunset: linear-gradient(135deg, #C9A961 0%, #D4AF37 50%, #E6D088 100%); + --gradient-ocean: linear-gradient(135deg, #A9B4C2 0%, #C5D3E0 100%); +} + +/* Deep Sea Theme - Dark, modern, electric blue accents */ +body.theme-deep-sea { + --primary-blue: #1E2022; + --warm-orange: #00BFFF; + --warm-amber: #1E90FF; + --warm-yellow: #4169E1; + --soft-cream: #2A2D30; + --text-dark: #E5E4E2; + --text-light: #C0C0C0; + --white: #1E2022; + --bg-light: #2A2D30; + --border: #3A3D40; + --gradient-warm: linear-gradient(135deg, #00BFFF 0%, #4169E1 100%); + --gradient-sunset: linear-gradient(135deg, #1E90FF 0%, #00BFFF 50%, #4169E1 100%); + --gradient-ocean: linear-gradient(135deg, #1E2022 0%, #2A2D30 100%); +} + +/* Monaco White Theme - Clean, minimalist, red accents */ +body.theme-monaco-white { + --primary-blue: #2C3E50; + --warm-orange: #E74C3C; + --warm-amber: #E67E22; + --warm-yellow: #F39C12; + --soft-cream: #F8F9FA; + --text-dark: #2C3E50; + --text-light: #7F8C8D; + --white: #FFFFFF; + --bg-light: #F8F9FA; + --border: #ECF0F1; + --gradient-warm: linear-gradient(135deg, #E74C3C 0%, #F39C12 100%); + --gradient-sunset: linear-gradient(135deg, #E67E22 0%, #E74C3C 50%, #F39C12 100%); + --gradient-ocean: linear-gradient(135deg, #2C3E50 0%, #34495E 100%); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* Global image fix to ensure images display */ +img { + max-width: 100%; + height: auto; + display: block; +} + +/* Lucide Icons Styling */ +[data-lucide] { + width: 24px; + height: 24px; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; + fill: none; + vertical-align: middle; +} + +.btn-icon { + width: 20px; + height: 20px; + display: inline-block; + margin-right: 8px; +} + +.spec-icon { + width: 20px; + height: 20px; + display: inline-block; + margin-right: 8px; + color: var(--warm-orange); +} + +.feature-icon i { + width: 32px; + height: 32px; + color: var(--warm-orange); +} + +.booking-icon i { + width: 48px; + height: 48px; + color: var(--warm-orange); + margin-bottom: 1rem; +} + +.footer-icon { + width: 16px; + height: 16px; + display: inline-block; + margin-right: 8px; + vertical-align: text-bottom; +} + +.social-links a { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.1); + transition: var(--transition); +} + +.social-links a:hover { + background: var(--warm-orange); +} + +.social-links a i { + width: 20px; + height: 20px; + color: white !important; + stroke: white !important; +} + +.social-links a svg { + stroke: white !important; + color: white !important; +} + +/* Star rating icons */ +.stars-icons { + display: inline-flex; + gap: 2px; +} + +.star-filled { + width: 16px; + height: 16px; + fill: var(--warm-yellow); + stroke: var(--warm-yellow); +} + +body { + font-family: var(--font-sans); + color: var(--text-dark); + background: var(--bg-light); + overflow-x: hidden; + line-height: 1.6; + transition: background 0.3s ease, color 0.3s ease; +} + +/* Smooth Scrolling */ +html { + scroll-behavior: smooth; +} + + +/* Navigation */ +.voyage-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + background: rgba(255, 255, 255, 0); + backdrop-filter: blur(0); + transition: var(--transition-slow); + padding: var(--space-md) 0; +} + +.voyage-nav.scrolled { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + padding: var(--space-sm) 0; +} + +.nav-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); + display: flex; + justify-content: space-between; + align-items: center; +} + +.nav-brand { + display: flex; + align-items: center; + gap: var(--space-sm); + font-family: var(--font-display); + font-size: 1.5rem; + font-weight: 700; + color: var(--white); + transition: var(--transition); +} + +.voyage-nav.scrolled .nav-brand { + color: var(--primary-blue); +} + +.nav-logo { + height: 50px; + width: auto; + object-fit: contain; +} + +.nav-links { + display: flex; + gap: var(--space-lg); + align-items: center; +} + +.nav-link { + color: var(--white); + text-decoration: none; + font-weight: 500; + transition: var(--transition); + position: relative; +} + +.voyage-nav.scrolled .nav-link { + color: var(--text-dark); +} + +.nav-link::after { + content: ''; + position: absolute; + bottom: -4px; + left: 0; + width: 0; + height: 2px; + background: var(--warm-orange); + transition: var(--transition); +} + +.nav-link:hover::after { + width: 100%; +} + +.nav-cta { + background: var(--gradient-warm); + color: var(--white) !important; + padding: 0.75rem 1.5rem; + border-radius: 50px; + transition: var(--transition); +} + +.nav-cta:hover { + transform: translateY(-2px); + box-shadow: 0 10px 20px rgba(249, 115, 22, 0.3); +} + +/* Theme Switcher */ +.theme-switcher { + position: relative; + margin-left: var(--space-md); +} + +.theme-btn { + background: transparent; + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: var(--transition); + color: var(--white); +} + +.voyage-nav.scrolled .theme-btn { + color: var(--primary-blue); + border-color: var(--primary-blue); +} + +.theme-btn:hover { + background: rgba(255, 255, 255, 0.1); + transform: rotate(180deg); +} + +.voyage-nav.scrolled .theme-btn:hover { + background: rgba(30, 58, 95, 0.1); +} + +.theme-dropdown { + position: absolute; + top: 50px; + right: 0; + background: var(--white); + border-radius: 12px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15); + padding: var(--space-sm); + min-width: 200px; + opacity: 0; + visibility: hidden; + transform: translateY(-10px); + transition: var(--transition); +} + +.theme-dropdown.active { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.theme-option { + display: flex; + align-items: center; + gap: var(--space-sm); + width: 100%; + padding: 0.75rem; + background: transparent; + border: none; + border-radius: 8px; + cursor: pointer; + transition: var(--transition); + text-align: left; +} + +.theme-option:hover { + background: var(--bg-light); +} + +.theme-colors { + display: flex; + gap: 2px; +} + +.theme-colors span { + width: 16px; + height: 16px; + border-radius: 50%; + border: 1px solid rgba(0, 0, 0, 0.1); +} + +/* Hero Section */ +.hero-voyage { + position: relative; + height: 100vh; + min-height: 600px; + overflow: hidden; +} + +.hero-video-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1; +} + +.hero-video { + width: 100%; + height: 100%; + object-fit: cover; +} + +.hero-image-fallback { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + z-index: -1; +} + +.hero-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 2; +} + +.gradient-warm { + background: linear-gradient(to bottom, + rgba(0, 31, 63, 0.3) 0%, + rgba(0, 31, 63, 0.5) 50%, + rgba(0, 31, 63, 0.7) 100%); +} + +.gradient-depth { + background: linear-gradient(to right, + rgba(220, 20, 60, 0.1) 0%, + transparent 100%); +} + +.hero-content { + position: relative; + z-index: 10; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + padding: var(--space-md); +} + +.trust-badge { + display: inline-flex; + align-items: center; + gap: var(--space-sm); + padding: 0.5rem 1.5rem; + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 50px; + color: var(--white) !important; + font-size: 0.875rem; + margin-bottom: var(--space-lg); + z-index: 10; + position: relative; +} + +/* Ensure the trust badge text is white */ +.trust-badge > span:last-child { + color: var(--white) !important; +} + +/* Make sure the headline is visible */ +.hero-headline { + display: block !important; + visibility: visible !important; +} + +.gradient-text { + display: inline-block !important; + visibility: visible !important; +} + +/* Make absolutely sure the trust badge text is white and visible */ +.trust-badge span:last-child { + display: inline !important; + color: white !important; + visibility: visible !important; +} + +.stars { + color: var(--warm-yellow); + font-size: 1rem; +} + +.hero-headline { + font-family: var(--font-display); + font-size: clamp(3rem, 8vw, 6rem); + font-weight: 900; + line-height: 1.1; + margin-bottom: var(--space-md); +} + +.gradient-text { + background: linear-gradient(135deg, + #ffffff 0%, + #f0f4f8 25%, + #e2e8f0 50%, + #cbd5e0 75%, + #94a3b8 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.hero-subtext { + font-size: 1.25rem; + color: rgba(255, 255, 255, 0.9); + max-width: 800px; + margin: 0 auto var(--space-lg); + font-weight: 300; + line-height: 1.8; +} + +.hero-actions { + display: flex; + gap: var(--space-md); + flex-wrap: wrap; + justify-content: center; +} + +.btn-primary-warm, +.btn-secondary-warm { + display: inline-flex; + align-items: center; + gap: var(--space-xs); + padding: 1rem 2rem; + font-size: 1.125rem; + font-weight: 600; + border: none; + border-radius: 50px; + cursor: pointer; + transition: var(--transition); + text-decoration: none; +} + +.btn-primary-warm { + background: var(--gradient-warm); + color: var(--white); + box-shadow: 0 10px 30px rgba(180, 139, 78, 0.3); +} + +.btn-primary-warm:hover { + transform: translateY(-3px); + box-shadow: 0 15px 40px rgba(180, 139, 78, 0.4); +} + +.btn-secondary-warm { + background: rgba(255, 255, 255, 0.1); + color: var(--white); + border: 2px solid rgba(255, 255, 255, 0.3); + backdrop-filter: blur(10px); +} + +.btn-secondary-warm:hover { + background: rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.5); + transform: translateY(-3px); +} + +.btn-icon { + font-size: 1.25rem; +} + +/* Scroll Indicator */ +.scroll-indicator { + position: absolute; + bottom: 2rem; + left: 50%; + transform: translateX(-50%); + color: rgba(255, 255, 255, 0.6); + font-size: 0.875rem; + animation: bounce 2s infinite; +} + +/* Updated scroll arrow styles */ +.scroll-arrow { + margin-top: 0.5rem; + display: flex; + justify-content: center; + align-items: center; +} + +.scroll-arrow i { + width: 32px; + height: 32px; + color: rgba(255, 255, 255, 0.6); + animation: arrow-bounce 1.5s infinite; +} + +@keyframes arrow-bounce { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(8px); + } +} + +/* Decorations */ +.hero-decoration { + position: absolute; + z-index: 5; +} + +.top-right { + top: 5rem; + right: 5rem; +} + +.bottom-left { + bottom: 8rem; + left: 5rem; +} + +.decoration-circle { + width: 80px; + height: 80px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 2rem; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.1); + animation: float 6s ease-in-out infinite; +} + +.decoration-circle.orange { + background: linear-gradient(135deg, + rgba(220, 20, 60, 0.2) 0%, + rgba(239, 68, 68, 0.2) 100%); +} + +.decoration-circle.blue { + background: linear-gradient(135deg, + rgba(0, 31, 63, 0.2) 0%, + rgba(0, 51, 102, 0.2) 100%); +} + +/* Welcome Section */ +.welcome-section { + padding: var(--space-2xl) 0; + background: linear-gradient(to bottom, var(--white) 0%, var(--bg-light) 100%); +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); +} + +.welcome-content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-2xl); + align-items: center; +} + +.section-title { + font-family: var(--font-display); + font-size: 3rem; + font-weight: 700; + margin-bottom: var(--space-md); + color: var(--primary-blue); +} + +.section-title.warm { + background: var(--gradient-warm); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.lead-text { + font-size: 1.25rem; + color: var(--text-light); + margin-bottom: var(--space-lg); + line-height: 1.8; +} + +.feature-list { + display: flex; + flex-direction: column; + gap: var(--space-md); +} + +.feature-item { + display: flex; + gap: var(--space-md); + align-items: flex-start; +} + +.feature-icon { + font-size: 2rem; + background: var(--gradient-warm); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.feature-item h4 { + font-size: 1.25rem; + margin-bottom: 0.25rem; + color: var(--text-dark); +} + +.feature-item p { + color: var(--text-light); +} + +.welcome-image { + position: relative; +} + +.rounded-image { + width: 100%; + border-radius: 20px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1); +} + +.image-badge { + position: absolute; + bottom: 2rem; + right: 2rem; + background: var(--gradient-warm); + color: var(--white); + padding: 1rem 1.5rem; + border-radius: 15px; + font-weight: 600; + display: flex; + flex-direction: column; + align-items: center; + box-shadow: 0 10px 30px rgba(180, 139, 78, 0.3); +} + +/* Fleet Showcase */ +.fleet-showcase { + padding: var(--space-2xl) 0; + background: var(--white); +} + +.section-header { + text-align: center; + margin-bottom: var(--space-2xl); +} + +.section-subtitle { + font-size: 1.25rem; + color: var(--text-light); + margin-top: var(--space-sm); +} + +.fleet-carousel { + position: relative; + max-width: 1000px; + margin: 0 auto; +} + +.yacht-card { + display: none; + grid-template-columns: 1fr 1fr; + gap: var(--space-lg); + align-items: center; + background: var(--white); + border-radius: 20px; + overflow: hidden; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08); +} + +.yacht-card.active { + display: grid; + animation: fadeIn 0.6s ease; +} + +.yacht-image-container { + position: relative; + height: 500px; + overflow: hidden; +} + +.yacht-image-container img { + width: 100%; + height: 100%; + object-fit: cover; + transition: var(--transition-slow); +} + +.yacht-card:hover .yacht-image-container img { + transform: scale(1.05); +} + +.yacht-badge { + position: absolute; + top: 2rem; + left: 2rem; + padding: 0.5rem 1rem; + border-radius: 50px; + font-weight: 600; + font-size: 0.875rem; + backdrop-filter: blur(10px); +} + +.yacht-badge.premium { + background: var(--gradient-warm); + color: var(--white); +} + +.yacht-badge.adventure { + background: var(--gradient-ocean); + color: var(--white); +} + +.yacht-details { + padding: var(--space-lg); +} + +.yacht-name { + font-family: var(--font-display); + font-size: 2.5rem; + margin-bottom: var(--space-sm); + color: var(--primary-blue); +} + +.yacht-description { + color: var(--text-light); + margin-bottom: var(--space-md); + line-height: 1.8; +} + +.yacht-specs { + display: flex; + flex-direction: column; + gap: var(--space-sm); + margin-bottom: var(--space-md); +} + +.spec { + display: flex; + align-items: center; + gap: var(--space-sm); +} + +.spec-icon { + font-size: 1.25rem; +} + +.yacht-pricing { + display: flex; + align-items: baseline; + gap: var(--space-xs); + margin-bottom: var(--space-md); +} + +.price-from { + color: var(--text-light); + font-size: 0.875rem; +} + +.price-amount { + font-size: 2rem; + font-weight: 700; + color: var(--warm-orange); +} + +.btn-book-yacht { + width: 100%; + padding: 1rem; + background: var(--gradient-warm); + color: var(--white); + border: none; + border-radius: 12px; + font-size: 1.125rem; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.btn-book-yacht:hover { + transform: translateY(-2px); + box-shadow: 0 10px 30px rgba(180, 139, 78, 0.3); +} + +.fleet-nav { + display: flex; + justify-content: center; + align-items: center; + gap: var(--space-md); + margin-top: var(--space-lg); +} + +.fleet-prev, +.fleet-next { + width: 50px; + height: 50px; + border-radius: 50%; + border: 2px solid var(--warm-orange); + background: var(--white); + color: var(--warm-orange); + font-size: 1.5rem; + cursor: pointer; + transition: var(--transition); +} + +.fleet-prev:hover, +.fleet-next:hover { + background: var(--gradient-warm); + color: var(--white); + border-color: transparent; +} + +.fleet-dots { + display: flex; + gap: var(--space-sm); +} + +.dot { + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--text-light); + opacity: 0.3; + cursor: pointer; + transition: var(--transition); +} + +.dot.active { + background: var(--warm-orange); + opacity: 1; + transform: scale(1.2); +} + +/* Services Section */ +.services-section { + padding: var(--space-2xl) 0; + background: linear-gradient(135deg, var(--soft-cream) 0%, var(--white) 100%); +} + +.services-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: var(--space-lg); + margin: var(--space-xl) 0; +} + +.service-card { + background: var(--white); + border-radius: 20px; + padding: var(--space-xl); + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08); + transition: var(--transition); + position: relative; + overflow: hidden; +} + +.service-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 5px; + background: var(--gradient-warm); +} + +.charter-service::before { + background: var(--gradient-ocean); +} + +.service-card:hover { + transform: translateY(-5px); + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.12); +} + +.service-icon-wrapper { + width: 80px; + height: 80px; + background: var(--gradient-warm); + border-radius: 20px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: var(--space-md); +} + +.charter-service .service-icon-wrapper { + background: var(--gradient-ocean); +} + +.service-icon { + width: 40px; + height: 40px; + color: white; +} + +.service-card h3 { + font-family: var(--font-display); + font-size: 2rem; + margin-bottom: var(--space-sm); + color: var(--text-dark); +} + +.service-card p { + color: var(--text-light); + margin-bottom: var(--space-md); + line-height: 1.6; +} + +.service-features { + list-style: none; + padding: 0; + margin: var(--space-md) 0; +} + +.service-features li { + padding: 0.5rem 0; + display: flex; + align-items: center; + color: var(--text-dark); +} + +.check-icon { + width: 20px; + height: 20px; + color: var(--warm-orange); + margin-right: 0.75rem; + flex-shrink: 0; +} + +.btn-service { + width: 100%; + padding: 1rem 2rem; + background: var(--gradient-warm); + color: white; + border: none; + border-radius: 12px; + font-weight: 600; + font-size: 1rem; + cursor: pointer; + transition: var(--transition); + margin-top: var(--space-md); +} + +.charter-service .btn-service { + background: var(--gradient-ocean); +} + +.btn-service:hover { + transform: scale(1.02); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); +} + +/* Service Stats */ +.service-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--space-md); + margin-top: var(--space-xl); + padding: var(--space-lg); + background: white; + border-radius: 20px; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05); +} + +.stat-item { + text-align: center; + padding: var(--space-md); +} + +.stat-icon { + width: 40px; + height: 40px; + color: var(--warm-orange); + margin-bottom: var(--space-sm); +} + +.stat-number { + display: block; + font-size: 2.5rem; + font-weight: 800; + color: var(--primary-blue); + font-family: var(--font-display); +} + +.stat-label { + display: block; + color: var(--text-light); + font-size: 0.875rem; + margin-top: 0.5rem; +} + +/* Experience Stories */ +.experience-stories { + padding: var(--space-2xl) 0; + background: var(--bg-light); +} + +.story-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); +} + +.section-title.center { + text-align: center; + margin-bottom: var(--space-2xl); +} + +.stories-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: var(--space-lg); +} + +.story-card { + background: var(--white); + border-radius: 20px; + overflow: hidden; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); + transition: var(--transition); +} + +.story-card:hover { + transform: translateY(-5px); + box-shadow: 0 15px 40px rgba(0, 0, 0, 0.12); +} + +.story-image { + position: relative; + height: 250px; + overflow: hidden; +} + +.story-image img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.story-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(to bottom, transparent 0%, rgba(0, 0, 0, 0.5) 100%); + display: flex; + align-items: flex-end; + padding: var(--space-md); +} + +.story-category { + background: var(--gradient-warm); + color: var(--white); + padding: 0.5rem 1rem; + border-radius: 50px; + font-size: 0.875rem; + font-weight: 600; +} + +.story-content { + padding: var(--space-md); +} + +.story-content h3 { + font-family: var(--font-display); + font-size: 1.5rem; + margin-bottom: var(--space-sm); + color: var(--text-dark); +} + +.story-content p { + color: var(--text-light); + line-height: 1.6; + margin-bottom: var(--space-sm); +} + +.story-link { + color: var(--warm-orange); + text-decoration: none; + font-weight: 600; + transition: var(--transition); +} + +.story-link:hover { + color: var(--warm-amber); +} + +/* Gallery Section */ +.gallery-section { + padding: var(--space-2xl) 0; + background: var(--white); +} + +.image-gallery { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--space-md); + margin-top: var(--space-lg); +} + +.gallery-item { + position: relative; + border-radius: 15px; + overflow: hidden; + height: 300px; + cursor: pointer; + transition: var(--transition); +} + +.gallery-item.large { + grid-column: span 2; + height: 400px; +} + +.gallery-item img { + width: 100%; + height: 100%; + object-fit: cover; + transition: var(--transition-slow); +} + +.gallery-overlay { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: linear-gradient(to top, rgba(0, 31, 63, 0.9) 0%, transparent 100%); + padding: var(--space-lg) var(--space-md) var(--space-md); + transform: translateY(100%); + transition: var(--transition); +} + +.gallery-caption { + color: var(--white); + font-size: 1.25rem; + font-weight: 600; +} + +.gallery-item:hover .gallery-overlay { + transform: translateY(0); +} + +.gallery-item:hover img { + transform: scale(1.1); +} + +@media (max-width: 768px) { + .gallery-item.large { + grid-column: span 1; + height: 300px; + } +} + +/* Booking CTA */ +.booking-cta { + padding: var(--space-2xl) 0; + background: linear-gradient(135deg, #001f3f 0%, #003366 100%); +} + +.booking-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); +} + +.booking-content { + text-align: center; +} + +.booking-title { + font-family: var(--font-display); + font-size: 3rem; + color: var(--white); + margin-bottom: var(--space-sm); +} + +.booking-subtitle { + font-size: 1.25rem; + color: rgba(255, 255, 255, 0.8); + margin-bottom: var(--space-2xl); +} + +.booking-options { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--space-lg); +} + +.booking-card { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 20px; + padding: var(--space-lg); + text-align: center; + transition: var(--transition); +} + +.booking-card:hover { + background: rgba(255, 255, 255, 0.15); + transform: translateY(-5px); +} + +.booking-card.featured { + background: var(--gradient-warm); + border: none; + transform: scale(1.05); +} + +.booking-icon { + font-size: 3rem; + margin-bottom: var(--space-sm); +} + +.booking-card h3 { + font-size: 1.5rem; + color: var(--white); + margin-bottom: var(--space-xs); +} + +.booking-card p { + color: rgba(255, 255, 255, 0.8); + margin-bottom: var(--space-md); +} + +.btn-booking { + width: 100%; + padding: 0.75rem 1.5rem; + background: rgba(255, 255, 255, 0.2); + color: var(--white); + border: 2px solid var(--white); + border-radius: 50px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.btn-booking:hover { + background: var(--white); + color: var(--primary-blue); +} + +.btn-booking.primary { + background: var(--white); + color: var(--warm-orange); + border-color: var(--white); +} + +.btn-booking.primary:hover { + background: var(--gradient-warm); + color: var(--white); +} + +/* Footer */ +.voyage-footer { + background: var(--primary-blue); + color: var(--white); + padding: var(--space-2xl) 0 var(--space-md); +} + +.footer-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); +} + +.footer-content { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + gap: var(--space-2xl); + margin-bottom: var(--space-lg); +} + +.footer-brand h3 { + font-family: var(--font-display); + font-size: 2rem; + margin-bottom: var(--space-sm); +} + +.footer-logo { + width: 50px; + height: 50px; + border-radius: 50%; + margin-bottom: var(--space-sm); +} + +.footer-brand p { + color: rgba(255, 255, 255, 0.7); + margin-bottom: var(--space-md); +} + +.social-links { + display: flex; + gap: var(--space-sm); +} + +.social-links a { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.1); + border-radius: 50%; + font-size: 1.25rem; + transition: var(--transition); +} + +.social-links a:hover { + background: var(--gradient-warm); + transform: translateY(-3px); +} + +.footer-links h4, +.footer-contact h4 { + margin-bottom: var(--space-md); +} + +/* Force social icons to be white */ +.voyage-footer .social-links a svg, +.voyage-footer .social-links a i { + stroke: white !important; + color: white !important; + fill: none !important; +} + +.footer-links a { + display: block; + color: rgba(255, 255, 255, 0.7); + text-decoration: none; + margin-bottom: var(--space-xs); + transition: var(--transition); +} + +.footer-links a:hover { + color: var(--warm-orange); +} + +.footer-contact p { + color: rgba(255, 255, 255, 0.7); + margin-bottom: var(--space-xs); +} + +.footer-bottom { + text-align: center; + padding-top: var(--space-lg); + border-top: 1px solid rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.5); +} + +/* Layout Switcher */ +.layout-switcher { + position: fixed; + bottom: 2rem; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 0.5rem; + background: var(--white); + padding: 0.5rem; + border-radius: 50px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); + z-index: 90; +} + +.layout-btn { + padding: 0.5rem 1rem; + background: transparent; + border: none; + border-radius: 50px; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: var(--transition); + color: var(--text-dark); +} + +.layout-btn:hover { + background: var(--bg-light); +} + +.layout-btn.active { + background: var(--gradient-warm); + color: var(--white); +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { + transform: translateX(-50%) translateY(0); + } + 40% { + transform: translateX(-50%) translateY(-10px); + } + 60% { + transform: translateX(-50%) translateY(-5px); + } +} + +@keyframes scroll { + 0% { + top: 8px; + opacity: 1; + } + 100% { + top: 20px; + opacity: 0; + } +} + +@keyframes float { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-20px); + } +} + +.animate-fade-in { + animation: fadeIn 0.8s ease; +} + +.animate-fade-up { + animation: fadeIn 0.8s ease 0.2s both; +} + +.animate-fade-up-delay { + animation: fadeIn 0.8s ease 0.4s both; +} + +.animate-fade-up-delay-2 { + animation: fadeIn 0.8s ease 0.6s both; +} + +/* Responsive */ +@media (max-width: 768px) { + .hero-headline { + font-size: 3rem; + } + + .hero-subtext { + font-size: 1rem; + } + + .hero-actions { + flex-direction: column; + width: 100%; + padding: 0 var(--space-md); + } + + .btn-primary-warm, + .btn-secondary-warm { + width: 100%; + } + + .welcome-content { + grid-template-columns: 1fr; + } + + .yacht-card { + grid-template-columns: 1fr; + } + + .footer-content { + grid-template-columns: 1fr; + text-align: center; + } + + .social-links { + justify-content: center; + } + + .top-right, + .bottom-left { + display: none; + } + + .nav-links { + display: none; + } +} + +/* Dark theme adjustments for Deep Sea */ +body.theme-deep-sea .welcome-section, +body.theme-deep-sea .fleet-showcase, +body.theme-deep-sea .experience-stories, +body.theme-deep-sea .booking-cta { + background: var(--bg-light); +} + +/* Gold theme is now the default - no overrides needed */ + +body.theme-deep-sea .yacht-card, +body.theme-deep-sea .story-card, +body.theme-deep-sea .booking-card { + background: var(--soft-cream); + color: var(--text-dark); +} + +body.theme-deep-sea .hero-content h1, +body.theme-deep-sea .hero-content p { + color: var(--text-dark); +} + +body.theme-deep-sea .voyage-nav:not(.scrolled) .nav-brand, +body.theme-deep-sea .voyage-nav:not(.scrolled) .nav-link { + color: var(--text-dark); +} + +body.theme-deep-sea .voyage-footer { + background: var(--primary-blue); + color: var(--text-dark); +} + +/* Theme-specific button colors */ +body.theme-coastal-dawn .btn-primary-warm, +body.theme-coastal-dawn .btn-secondary-warm { + background: var(--warm-orange); + color: var(--white); +} + +body.theme-deep-sea .btn-primary-warm, +body.theme-deep-sea .btn-secondary-warm { + background: var(--warm-orange); + color: var(--white); +} + +body.theme-monaco-white .btn-primary-warm, +body.theme-monaco-white .btn-secondary-warm { + background: var(--warm-orange); + color: var(--white); +} \ No newline at end of file diff --git a/apps/website/components/AppFooter.vue b/apps/website/components/AppFooter.vue new file mode 100644 index 0000000..825a0e6 --- /dev/null +++ b/apps/website/components/AppFooter.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/apps/website/components/AppNavbar.vue b/apps/website/components/AppNavbar.vue new file mode 100644 index 0000000..ca4c484 --- /dev/null +++ b/apps/website/components/AppNavbar.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/apps/website/components/BookingSection.vue b/apps/website/components/BookingSection.vue new file mode 100644 index 0000000..8a40afc --- /dev/null +++ b/apps/website/components/BookingSection.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/apps/website/components/GallerySection.vue b/apps/website/components/GallerySection.vue new file mode 100644 index 0000000..26de91d --- /dev/null +++ b/apps/website/components/GallerySection.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/apps/website/components/HeroSection.vue b/apps/website/components/HeroSection.vue new file mode 100644 index 0000000..6f0b65d --- /dev/null +++ b/apps/website/components/HeroSection.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/apps/website/components/ServicesSection.vue b/apps/website/components/ServicesSection.vue new file mode 100644 index 0000000..f467c3d --- /dev/null +++ b/apps/website/components/ServicesSection.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/apps/website/components/TestimonialsSection.vue b/apps/website/components/TestimonialsSection.vue new file mode 100644 index 0000000..fc22655 --- /dev/null +++ b/apps/website/components/TestimonialsSection.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/apps/website/components/TrustIndicators.vue b/apps/website/components/TrustIndicators.vue new file mode 100644 index 0000000..69224c3 --- /dev/null +++ b/apps/website/components/TrustIndicators.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/apps/website/components/WelcomeSection.vue b/apps/website/components/WelcomeSection.vue new file mode 100644 index 0000000..9513844 --- /dev/null +++ b/apps/website/components/WelcomeSection.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/apps/website/composables/useIntersectionAnimations.js b/apps/website/composables/useIntersectionAnimations.js new file mode 100644 index 0000000..c98c064 --- /dev/null +++ b/apps/website/composables/useIntersectionAnimations.js @@ -0,0 +1,96 @@ +import { ref, onMounted, onBeforeUnmount } from 'vue' + +export const useIntersectionAnimations = (threshold = 0.1) => { + const elements = ref([]) + let observer = null + + const observerOptions = { + threshold, + rootMargin: '0px 0px -100px 0px' + } + + const animateElement = (entry) => { + if (entry.isIntersecting) { + entry.target.classList.add('animate-in') + entry.target.style.opacity = '1' + entry.target.style.transform = 'translateY(0)' + + // Unobserve after animation to improve performance + if (observer) { + observer.unobserve(entry.target) + } + } + } + + const observeElements = () => { + // Check for reduced motion preference + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches + + if (prefersReducedMotion) { + // Skip animations for users who prefer reduced motion + document.querySelectorAll('[data-animate]').forEach(el => { + el.style.opacity = '1' + el.style.transform = 'none' + }) + return + } + + observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + animateElement(entry) + }) + }, observerOptions) + + // Find all elements with data-animate attribute + document.querySelectorAll('[data-animate]').forEach(el => { + // Set initial state + el.style.opacity = '0' + el.style.transition = 'all 0.6s cubic-bezier(0.4, 0, 0.2, 1)' + + const animationType = el.dataset.animate + + switch (animationType) { + case 'fade-up': + el.style.transform = 'translateY(30px)' + break + case 'fade-in': + // Just opacity, no transform + break + case 'scale-in': + el.style.transform = 'scale(0.95)' + break + case 'slide-left': + el.style.transform = 'translateX(50px)' + break + case 'slide-right': + el.style.transform = 'translateX(-50px)' + break + default: + el.style.transform = 'translateY(20px)' + } + + // Add delay if specified + if (el.dataset.animateDelay) { + el.style.transitionDelay = el.dataset.animateDelay + } + + observer.observe(el) + elements.value.push(el) + }) + } + + onMounted(() => { + // Wait for DOM to be ready + setTimeout(observeElements, 100) + }) + + onBeforeUnmount(() => { + if (observer) { + observer.disconnect() + } + }) + + return { + elements + } +} \ No newline at end of file diff --git a/apps/website/composables/useParallax.js b/apps/website/composables/useParallax.js new file mode 100644 index 0000000..182962e --- /dev/null +++ b/apps/website/composables/useParallax.js @@ -0,0 +1,47 @@ +import { ref, onMounted, onBeforeUnmount } from 'vue' + +export const useParallax = (elementRef, speed = 0.5) => { + const transform = ref('') + let ticking = false + + const handleScroll = () => { + if (!ticking) { + window.requestAnimationFrame(() => { + if (elementRef.value) { + const scrolled = window.pageYOffset + const rate = scrolled * speed + transform.value = `translateY(${rate}px)` + elementRef.value.style.transform = transform.value + } + ticking = false + }) + ticking = true + } + } + + onMounted(() => { + // Add will-change for performance + if (elementRef.value) { + elementRef.value.style.willChange = 'transform' + } + + // Check for reduced motion preference + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches + + if (!prefersReducedMotion) { + window.addEventListener('scroll', handleScroll, { passive: true }) + handleScroll() // Initial calculation + } + }) + + onBeforeUnmount(() => { + window.removeEventListener('scroll', handleScroll) + if (elementRef.value) { + elementRef.value.style.willChange = 'auto' + } + }) + + return { + transform + } +} \ No newline at end of file diff --git a/apps/website/composables/useRipple.js b/apps/website/composables/useRipple.js new file mode 100644 index 0000000..d92f57a --- /dev/null +++ b/apps/website/composables/useRipple.js @@ -0,0 +1,79 @@ +import { onMounted, onBeforeUnmount } from 'vue' + +export const useRipple = (buttonSelector = '.btn-primary-warm, .btn-secondary-warm, .btn-booking') => { + let buttons = [] + + const createRipple = (event) => { + const button = event.currentTarget + + // Check for reduced motion preference + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches + if (prefersReducedMotion) return + + const circle = document.createElement('span') + const diameter = Math.max(button.clientWidth, button.clientHeight) + const radius = diameter / 2 + + // Calculate position relative to button + const rect = button.getBoundingClientRect() + const x = event.clientX - rect.left - radius + const y = event.clientY - rect.top - radius + + circle.style.width = circle.style.height = `${diameter}px` + circle.style.left = `${x}px` + circle.style.top = `${y}px` + circle.classList.add('ripple') + + // Remove any existing ripple + const ripple = button.getElementsByClassName('ripple')[0] + if (ripple) { + ripple.remove() + } + + button.appendChild(circle) + + // Remove ripple after animation + setTimeout(() => { + circle.remove() + }, 600) + } + + const initRipple = () => { + buttons = document.querySelectorAll(buttonSelector) + + buttons.forEach(button => { + // Ensure button has position relative and overflow hidden + button.style.position = 'relative' + button.style.overflow = 'hidden' + + // Add event listener + button.addEventListener('click', createRipple) + }) + } + + const destroyRipple = () => { + buttons.forEach(button => { + button.removeEventListener('click', createRipple) + }) + } + + onMounted(() => { + // Wait for DOM to be ready + setTimeout(initRipple, 100) + + // Re-init if new buttons are added dynamically + const observer = new MutationObserver(() => { + destroyRipple() + initRipple() + }) + + observer.observe(document.body, { + childList: true, + subtree: true + }) + }) + + onBeforeUnmount(() => { + destroyRipple() + }) +} \ No newline at end of file diff --git a/apps/website/docker-compose.yml b/apps/website/docker-compose.yml new file mode 100644 index 0000000..4aff625 --- /dev/null +++ b/apps/website/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + harborsmith-website: + build: + context: . + dockerfile: Dockerfile + container_name: harborsmith-website + ports: + - "3001:80" + restart: unless-stopped + networks: + - harborsmith-network + labels: + - "traefik.enable=true" + - "traefik.http.routers.harborsmith-website.rule=Host(`localhost`)" + - "traefik.http.services.harborsmith-website.loadbalancer.server.port=80" + +networks: + harborsmith-network: + external: true + name: harborsmith_default \ No newline at end of file diff --git a/apps/website/layouts/default.vue b/apps/website/layouts/default.vue new file mode 100644 index 0000000..a8e3d60 --- /dev/null +++ b/apps/website/layouts/default.vue @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/apps/website/nginx.conf b/apps/website/nginx.conf new file mode 100644 index 0000000..c06f180 --- /dev/null +++ b/apps/website/nginx.conf @@ -0,0 +1,99 @@ +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip Settings + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml application/atom+xml image/svg+xml text/x-js text/x-cross-domain-policy application/x-font-ttf application/x-font-opentype application/vnd.ms-fontobject image/x-icon; + gzip_disable "msie6"; + + # Cache Settings + map $sent_http_content_type $expires { + default off; + text/html epoch; + text/css max; + application/javascript max; + application/json off; + ~image/ max; + ~font/ max; + } + + server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' https:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https: fonts.googleapis.com; font-src 'self' https: fonts.gstatic.com data:; img-src 'self' https: data:; media-src 'self' https: *.pexels.com; connect-src 'self' https:;" always; + + # Cache control + expires $expires; + + # Main location + location / { + try_files $uri $uri/ /index.html; + } + + # Static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|otf|mp4|webm)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # API proxy (if needed for future API integration) + location /api/ { + proxy_pass http://api:3000/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # Error pages + error_page 404 /404.html; + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + } +} \ No newline at end of file diff --git a/apps/website/nuxt.config.ts b/apps/website/nuxt.config.ts new file mode 100644 index 0000000..d95af9a --- /dev/null +++ b/apps/website/nuxt.config.ts @@ -0,0 +1,159 @@ +import { existsSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' + +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + compatibilityDate: '2024-04-03', + devtools: { enabled: true }, + + // Static Site Generation + ssr: true, + nitro: { + prerender: { + routes: ['/'], + crawlLinks: true + } + }, + + // Modules + modules: [ + '@nuxt/image', + '@nuxtjs/tailwindcss', + '@nuxtjs/google-fonts', + // '@nuxtjs/seo', // Temporarily disabled - incompatible with Nuxt 3.19.2 + '@vueuse/nuxt', + '@vueuse/motion/nuxt' + ], + + // Google Fonts + googleFonts: { + families: { + 'Inter': [300, 400, 500, 600, 700, 800], + 'Playfair+Display': [400, 700, 900] + }, + display: 'swap', + preload: true, + prefetch: false, + preconnect: true + }, + + // SEO - disabled temporarily + // site: { + // url: 'https://harborsmith.com', + // name: 'Harbor Smith', + // description: 'Premium yacht charter and maintenance services in San Francisco Bay', + // defaultLocale: 'en' + // }, + + // ogImage: { + // enabled: false + // }, + + // Image optimization + image: { + quality: 90, + format: ['webp', 'jpg'], + screens: { + xs: 320, + sm: 640, + md: 768, + lg: 1024, + xl: 1280, + xxl: 1536, + '2xl': 1536 + } + }, + + // Tailwind CSS + tailwindcss: { + exposeConfig: true, + viewer: false, + config: { + content: [], + theme: { + extend: { + colors: { + 'harbor-blue': '#001f3f', + 'harbor-navy': '#1e3a5f', + 'harbor-gold': '#b48b4e', + 'harbor-amber': '#9d7943', + 'harbor-yellow': '#c9a56f', + 'harbor-light': '#f0f0f0' + }, + fontFamily: { + sans: ['Inter', 'sans-serif'], + serif: ['Playfair Display', 'serif'] + }, + animation: { + 'fade-in': 'fadeIn 0.6s ease-out', + 'slide-up': 'slideUp 0.6s ease-out', + 'scale-in': 'scaleIn 0.5s ease-out' + }, + keyframes: { + fadeIn: { + '0%': { opacity: '0' }, + '100%': { opacity: '1' } + }, + slideUp: { + '0%': { transform: 'translateY(30px)', opacity: '0' }, + '100%': { transform: 'translateY(0)', opacity: '1' } + }, + scaleIn: { + '0%': { transform: 'scale(0.9)', opacity: '0' }, + '100%': { transform: 'scale(1)', opacity: '1' } + } + }, + backgroundImage: { + 'gradient-warm': 'linear-gradient(135deg, #b48b4e 0%, #c9a56f 100%)', + 'gradient-blue': 'linear-gradient(135deg, #001f3f 0%, #1e3a5f 100%)' + } + } + } + } + }, + + // App configuration + app: { + head: { + title: 'Harbor Smith - Premium Yacht Charter & Maintenance', + meta: [ + { charset: 'utf-8' }, + { name: 'viewport', content: 'width=device-width, initial-scale=1' }, + { name: 'description', content: 'Experience luxury yacht charters and professional maintenance services in San Francisco Bay with Harbor Smith.' }, + { name: 'format-detection', content: 'telephone=no' } + ], + link: [ + { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } + ] + }, + pageTransition: { name: 'page', mode: 'out-in' }, + layoutTransition: { name: 'layout', mode: 'out-in' } + }, + + // CSS + css: [ + '~/assets/css/voyage-layout.css', + '~/assets/css/themes.css', + '~/assets/css/main.css' + ], + + // Runtime config + runtimeConfig: { + public: { + siteUrl: process.env.NUXT_PUBLIC_SITE_URL || 'https://harborsmith.com' + } + }, + + hooks: { + 'prepare:types': () => { + const buildDir = join(process.cwd(), '.nuxt') + const content = JSON.stringify({ extends: './tsconfig.json' }, null, 2) + for (const file of ['tsconfig.app.json', 'tsconfig.shared.json']) { + const target = join(buildDir, file) + if (!existsSync(target)) { + writeFileSync(target, content + '\n', 'utf8') + } + } + } + } +}) \ No newline at end of file diff --git a/apps/website/package-lock.json b/apps/website/package-lock.json new file mode 100644 index 0000000..44c1be0 --- /dev/null +++ b/apps/website/package-lock.json @@ -0,0 +1,13939 @@ +{ + "name": "@harborsmith/website", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@harborsmith/website", + "version": "1.0.0", + "hasInstallScript": true, + "dependencies": { + "@iconify-json/lucide": "^1.2.68", + "@vueuse/motion": "^2.2.6", + "@vueuse/nuxt": "^11.3.0", + "lucide-vue-next": "^0.544.0", + "nuxt": "^3.15.0", + "unenv": "^2.0.0-rc.21", + "vue": "latest", + "vue-router": "latest" + }, + "devDependencies": { + "@nuxt/image": "^1.8.1", + "@nuxtjs/google-fonts": "^3.2.0", + "@nuxtjs/seo": "^2.0.0", + "@nuxtjs/tailwindcss": "^6.12.2", + "@types/node": "^20", + "typescript": "^5.7.2" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@antfu/utils": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-8.1.1.tgz", + "integrity": "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", + "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.0.tgz", + "integrity": "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "mime": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@cloudflare/kv-asset-handler/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", + "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/accept-negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz", + "integrity": "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@fingerprintjs/botd": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@fingerprintjs/botd/-/botd-1.9.1.tgz", + "integrity": "sha512-7kv3Yolsx9E56i+L1hCEcupH5yqcI5cmVktxy6B0K7rimaH5qDXwsiA5FL+fkxeUny7XQKn7p13HvK7ofDZB3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@iconify-json/lucide": { + "version": "1.2.68", + "resolved": "https://registry.npmjs.org/@iconify-json/lucide/-/lucide-1.2.68.tgz", + "integrity": "sha512-lR5xNJdn2CT0iR7lM25G4SewBO4G2hbr3fTWOc3AE9BspflEcneh02E3l9TBaCU/JOHozTJevWLrxBGypD7Tng==", + "license": "ISC", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@ioredis/commands": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.4.0.tgz", + "integrity": "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@koa/router": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-12.0.2.tgz", + "integrity": "sha512-sYcHglGKTxGF+hQ6x67xDfkE9o+NhVlRHBqq6gLywaMc6CojK/5vFZByphdonKinYlMLkEkacm+HEse9HzwgTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "http-errors": "^2.0.0", + "koa-compose": "^4.1.0", + "methods": "^1.1.2", + "path-to-regexp": "^6.3.0" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "license": "MIT" + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.0.tgz", + "integrity": "sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==", + "license": "BSD-3-Clause", + "dependencies": { + "consola": "^3.2.3", + "detect-libc": "^2.0.0", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^2.6.7", + "nopt": "^8.0.0", + "semver": "^7.5.3", + "tar": "^7.4.0" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/detect-libc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz", + "integrity": "sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.5.tgz", + "integrity": "sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nuxt/cli": { + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@nuxt/cli/-/cli-3.28.0.tgz", + "integrity": "sha512-WQ751WxWLBIeH3TDFt/LWQ2znyAKxpR5+gpv80oerwnVQs4GKajAfR6dIgExXZkjaPUHEFv2lVD9vM+frbprzw==", + "license": "MIT", + "dependencies": { + "c12": "^3.2.0", + "citty": "^0.1.6", + "clipboardy": "^4.0.0", + "confbox": "^0.2.2", + "consola": "^3.4.2", + "defu": "^6.1.4", + "exsolve": "^1.0.7", + "fuse.js": "^7.1.0", + "get-port-please": "^3.2.0", + "giget": "^2.0.0", + "h3": "^1.15.4", + "httpxy": "^0.1.7", + "jiti": "^2.5.1", + "listhen": "^1.9.0", + "nypm": "^0.6.1", + "ofetch": "^1.4.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "scule": "^1.3.0", + "semver": "^7.7.2", + "std-env": "^3.9.0", + "tinyexec": "^1.0.1", + "ufo": "^1.6.1", + "youch": "^4.1.0-beta.11" + }, + "bin": { + "nuxi": "bin/nuxi.mjs", + "nuxi-ng": "bin/nuxi.mjs", + "nuxt": "bin/nuxi.mjs", + "nuxt-cli": "bin/nuxi.mjs" + }, + "engines": { + "node": "^16.10.0 || >=18.0.0" + } + }, + "node_modules/@nuxt/cli/node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/@nuxt/devalue": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@nuxt/devalue/-/devalue-2.0.2.tgz", + "integrity": "sha512-GBzP8zOc7CGWyFQS6dv1lQz8VVpz5C2yRszbXufwG/9zhStTIH50EtD87NmWbTMwXDvZLNg8GIpb1UFdH93JCA==", + "license": "MIT" + }, + "node_modules/@nuxt/devtools": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@nuxt/devtools/-/devtools-2.6.3.tgz", + "integrity": "sha512-n+8we7pr0tNl6w+KfbFDXZsYpWIYL4vG/daIdRF66lQ6fLyQy/CcxDAx8+JNu3Ew96RjuBtWRSbCCv454L5p0Q==", + "license": "MIT", + "dependencies": { + "@nuxt/devtools-kit": "2.6.3", + "@nuxt/devtools-wizard": "2.6.3", + "@nuxt/kit": "^3.18.1", + "@vue/devtools-core": "^7.7.7", + "@vue/devtools-kit": "^7.7.7", + "birpc": "^2.5.0", + "consola": "^3.4.2", + "destr": "^2.0.5", + "error-stack-parser-es": "^1.0.5", + "execa": "^8.0.1", + "fast-npm-meta": "^0.4.6", + "get-port-please": "^3.2.0", + "hookable": "^5.5.3", + "image-meta": "^0.2.1", + "is-installed-globally": "^1.0.0", + "launch-editor": "^2.11.1", + "local-pkg": "^1.1.2", + "magicast": "^0.3.5", + "nypm": "^0.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.3.0", + "semver": "^7.7.2", + "simple-git": "^3.28.0", + "sirv": "^3.0.1", + "structured-clone-es": "^1.0.0", + "tinyglobby": "^0.2.14", + "vite-plugin-inspect": "^11.3.2", + "vite-plugin-vue-tracer": "^1.0.0", + "which": "^5.0.0", + "ws": "^8.18.3" + }, + "bin": { + "devtools": "cli.mjs" + }, + "peerDependencies": { + "vite": ">=6.0" + } + }, + "node_modules/@nuxt/devtools-kit": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@nuxt/devtools-kit/-/devtools-kit-2.6.3.tgz", + "integrity": "sha512-cDmai3Ws6AbJlYy1p4CCwc718cfbqtAjXe6oEc6q03zoJnvX1PsvKUfmU+yuowfqTSR6DZRmH4SjCBWuMjgaKQ==", + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.18.1", + "execa": "^8.0.1" + }, + "peerDependencies": { + "vite": ">=6.0" + } + }, + "node_modules/@nuxt/devtools-wizard": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@nuxt/devtools-wizard/-/devtools-wizard-2.6.3.tgz", + "integrity": "sha512-FWXPkuJ1RUp+9nWP5Vvk29cJPNtm4OO38bgr9G8vGbqcRznzgaSODH/92c8sm2dKR7AF+9MAYLL+BexOWOkljQ==", + "license": "MIT", + "dependencies": { + "consola": "^3.4.2", + "diff": "^8.0.2", + "execa": "^8.0.1", + "magicast": "^0.3.5", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "prompts": "^2.4.2", + "semver": "^7.7.2" + }, + "bin": { + "devtools-wizard": "cli.mjs" + } + }, + "node_modules/@nuxt/devtools/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@nuxt/devtools/node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/@nuxt/devtools/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@nuxt/image": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@nuxt/image/-/image-1.11.0.tgz", + "integrity": "sha512-4kzhvb2tJfxMsa/JZeYn1sMiGbx2J/S6BQrQSdXNsHgSvywGVkFhTiQGjoP6O49EsXyAouJrer47hMeBcTcfXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.18.0", + "consola": "^3.4.2", + "defu": "^6.1.4", + "h3": "^1.15.3", + "image-meta": "^0.2.1", + "knitwork": "^1.2.0", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "std-env": "^3.9.0", + "ufo": "^1.6.1" + }, + "engines": { + "node": ">=18.20.6" + }, + "optionalDependencies": { + "ipx": "^2.1.1" + } + }, + "node_modules/@nuxt/kit": { + "version": "3.19.2", + "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.19.2.tgz", + "integrity": "sha512-+QiqO0WcIxsKLUqXdVn3m4rzTRm2fO9MZgd330utCAaagGmHsgiMJp67kE14boJEPutnikfz3qOmrzBnDIHUUg==", + "license": "MIT", + "dependencies": { + "c12": "^3.2.0", + "consola": "^3.4.2", + "defu": "^6.1.4", + "destr": "^2.0.5", + "errx": "^0.1.0", + "exsolve": "^1.0.7", + "ignore": "^7.0.5", + "jiti": "^2.5.1", + "klona": "^2.0.6", + "knitwork": "^1.2.0", + "mlly": "^1.8.0", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "rc9": "^2.1.2", + "scule": "^1.3.0", + "semver": "^7.7.2", + "std-env": "^3.9.0", + "tinyglobby": "^0.2.15", + "ufo": "^1.6.1", + "unctx": "^2.4.1", + "unimport": "^5.2.0", + "untyped": "^2.0.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@nuxt/schema": { + "version": "3.19.2", + "resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.19.2.tgz", + "integrity": "sha512-kMN2oIfrsMc8ACrRweYRG7Q44/KuHG5y7L+4szQhfOgN78OiYkxiM/nSsLH0K2bJq8Eavg+WGfgACj4Lsy+YqQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "^3.5.21", + "consola": "^3.4.2", + "defu": "^6.1.4", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "std-env": "^3.9.0", + "ufo": "1.6.1" + }, + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/@nuxt/telemetry": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/@nuxt/telemetry/-/telemetry-2.6.6.tgz", + "integrity": "sha512-Zh4HJLjzvm3Cq9w6sfzIFyH9ozK5ePYVfCUzzUQNiZojFsI2k1QkSBrVI9BGc6ArKXj/O6rkI6w7qQ+ouL8Cag==", + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.15.4", + "citty": "^0.1.6", + "consola": "^3.4.2", + "destr": "^2.0.3", + "dotenv": "^16.4.7", + "git-url-parse": "^16.0.1", + "is-docker": "^3.0.0", + "ofetch": "^1.4.1", + "package-manager-detector": "^1.1.0", + "pathe": "^2.0.3", + "rc9": "^2.1.2", + "std-env": "^3.8.1" + }, + "bin": { + "nuxt-telemetry": "bin/nuxt-telemetry.mjs" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@nuxt/telemetry/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@nuxt/vite-builder": { + "version": "3.19.2", + "resolved": "https://registry.npmjs.org/@nuxt/vite-builder/-/vite-builder-3.19.2.tgz", + "integrity": "sha512-SESdHAKWy63RKG3uqrBEJvTbfkmEsiggmDEqjEwhBP2fe0E6bGTmLpX/Eh4AuOLbZuZOmir984OHFiM/Q/MLhg==", + "license": "MIT", + "dependencies": { + "@nuxt/kit": "3.19.2", + "@rollup/plugin-replace": "^6.0.2", + "@vitejs/plugin-vue": "^6.0.1", + "@vitejs/plugin-vue-jsx": "^5.1.1", + "autoprefixer": "^10.4.21", + "consola": "^3.4.2", + "cssnano": "^7.1.1", + "defu": "^6.1.4", + "esbuild": "^0.25.9", + "escape-string-regexp": "^5.0.0", + "exsolve": "^1.0.7", + "externality": "^1.0.2", + "get-port-please": "^3.2.0", + "h3": "^1.15.4", + "jiti": "^2.5.1", + "knitwork": "^1.2.0", + "magic-string": "^0.30.19", + "mlly": "^1.8.0", + "mocked-exports": "^0.1.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.0.0", + "pkg-types": "^2.3.0", + "postcss": "^8.5.6", + "rollup-plugin-visualizer": "^6.0.3", + "std-env": "^3.9.0", + "ufo": "^1.6.1", + "unenv": "^2.0.0-rc.21", + "vite": "^7.1.5", + "vite-node": "^3.2.4", + "vite-plugin-checker": "^0.10.3", + "vue-bundle-renderer": "^2.1.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vue": "^3.3.4" + } + }, + "node_modules/@nuxtjs/google-fonts": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@nuxtjs/google-fonts/-/google-fonts-3.2.0.tgz", + "integrity": "sha512-cGAjDJoeQ2jm6VJCo4AtSmKO6KjsbO9RSLj8q261fD0lMVNMZCxkCxBkg8L0/2Vfgp+5QBHWVXL71p1tiybJFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.10.3", + "google-fonts-helper": "^3.5.0", + "pathe": "^1.1.2" + } + }, + "node_modules/@nuxtjs/google-fonts/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nuxtjs/robots": { + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/@nuxtjs/robots/-/robots-5.5.5.tgz", + "integrity": "sha512-ZJZQHFrahJITNakvRx2+TAHH2xQMO0BHYANlPVQ2ZU7s3wQMnOjaYAyAuTjzbHlLqUqurpQQtYT3WNrsl3QpJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fingerprintjs/botd": "^1.9.1", + "@nuxt/kit": "^4.1.2", + "consola": "^3.4.2", + "defu": "^6.1.4", + "nuxt-site-config": "^3.2.5", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "sirv": "^3.0.2", + "std-env": "^3.9.0", + "ufo": "^1.6.1" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@nuxtjs/robots/node_modules/@nuxt/kit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.1.2.tgz", + "integrity": "sha512-P5q41xeEOa6ZQC0PvIP7TSBmOAMxXK4qihDcCbYIJq8RcVsEPbGZVlidmxE6EOw1ucSyodq9nbV31FAKwoL4NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "c12": "^3.2.0", + "consola": "^3.4.2", + "defu": "^6.1.4", + "destr": "^2.0.5", + "errx": "^0.1.0", + "exsolve": "^1.0.7", + "ignore": "^7.0.5", + "jiti": "^2.5.1", + "klona": "^2.0.6", + "mlly": "^1.8.0", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "rc9": "^2.1.2", + "scule": "^1.3.0", + "semver": "^7.7.2", + "std-env": "^3.9.0", + "tinyglobby": "^0.2.15", + "ufo": "^1.6.1", + "unctx": "^2.4.1", + "unimport": "^5.2.0", + "untyped": "^2.0.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@nuxtjs/seo": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@nuxtjs/seo/-/seo-2.2.0.tgz", + "integrity": "sha512-3BdQY3hnZYUrmee68oqEw4HeABYA310vk/3ckiR/zAiDqlHSd7PiMQYMl94wllivjFxWDPMH22+pEIhlXSSO7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.15.4", + "@nuxtjs/robots": "^5.2.4", + "@nuxtjs/sitemap": "^7.2.6", + "nuxt-link-checker": "^4.1.1", + "nuxt-og-image": "^4.1.4", + "nuxt-schema-org": "^4.1.2", + "nuxt-seo-utils": "^6.0.11", + "nuxt-site-config": "^3.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@nuxtjs/sitemap": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@nuxtjs/sitemap/-/sitemap-7.4.7.tgz", + "integrity": "sha512-DUhX92lnCJD6tpghUmfmRIsSIoiXMS2SQ2Yd9Tg1+SnZskiKX+DGwLeAeHX8r0/9Pl/bTDpmYhs1snWcCoIkXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/devtools-kit": "^2.6.3", + "@nuxt/kit": "^4.1.2", + "chalk": "^5.6.2", + "defu": "^6.1.4", + "fast-xml-parser": "^5.2.5", + "h3-compression": "^0.3.2", + "nuxt-site-config": "^3.2.5", + "ofetch": "^1.4.1", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "radix3": "^1.1.2", + "semver": "^7.7.2", + "sirv": "^3.0.2", + "std-env": "^3.9.0", + "ufo": "^1.6.1", + "ultrahtml": "^1.6.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@nuxtjs/sitemap/node_modules/@nuxt/kit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.1.2.tgz", + "integrity": "sha512-P5q41xeEOa6ZQC0PvIP7TSBmOAMxXK4qihDcCbYIJq8RcVsEPbGZVlidmxE6EOw1ucSyodq9nbV31FAKwoL4NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "c12": "^3.2.0", + "consola": "^3.4.2", + "defu": "^6.1.4", + "destr": "^2.0.5", + "errx": "^0.1.0", + "exsolve": "^1.0.7", + "ignore": "^7.0.5", + "jiti": "^2.5.1", + "klona": "^2.0.6", + "mlly": "^1.8.0", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "rc9": "^2.1.2", + "scule": "^1.3.0", + "semver": "^7.7.2", + "std-env": "^3.9.0", + "tinyglobby": "^0.2.15", + "ufo": "^1.6.1", + "unctx": "^2.4.1", + "unimport": "^5.2.0", + "untyped": "^2.0.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@nuxtjs/tailwindcss": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@nuxtjs/tailwindcss/-/tailwindcss-6.14.0.tgz", + "integrity": "sha512-30RyDK++LrUVRgc2A85MktGWIZoRQgeQKjE4CjjD64OXNozyl+4ScHnnYgqVToMM6Ch2ZG2W4wV2J0EN6F0zkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.16.0", + "autoprefixer": "^10.4.20", + "c12": "^3.0.2", + "consola": "^3.4.0", + "defu": "^6.1.4", + "h3": "^1.15.1", + "klona": "^2.0.6", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "pkg-types": "^2.1.0", + "postcss": "^8.5.3", + "postcss-nesting": "^13.0.1", + "tailwind-config-viewer": "^2.0.4", + "tailwindcss": "~3.4.17", + "ufo": "^1.5.4", + "unctx": "^2.4.1" + } + }, + "node_modules/@oxc-minify/binding-android-arm64": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-android-arm64/-/binding-android-arm64-0.87.0.tgz", + "integrity": "sha512-ZbJmAfXvNAamOSnXId3BiM3DiuzlD1isqKjtmRFb/hpvChHHA23FSPrFcO16w+ugZKg33sZ93FinFkKtlC4hww==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-minify/binding-darwin-arm64": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-darwin-arm64/-/binding-darwin-arm64-0.87.0.tgz", + "integrity": "sha512-ewmNsTY8YbjWOI8+EOWKTVATOYvG4Qq4zQHH5VFBeqhQPVusY1ORD6Ei+BijVKrnlbpjibLlkTl8IWqXCGK89A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-minify/binding-darwin-x64": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-darwin-x64/-/binding-darwin-x64-0.87.0.tgz", + "integrity": "sha512-qDH4w4EYttSC3Cs2VCh+CiMYKrcL2SNmnguBZXoUXe/RNk3csM+RhgcwdpX687xGvOhTFhH5PCIA84qh3ZpIbQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-minify/binding-freebsd-x64": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-freebsd-x64/-/binding-freebsd-x64-0.87.0.tgz", + "integrity": "sha512-5kxjHlSev2A09rDeITk+LMHxSrU3Iu8pUb0Zp4m+ul8FKlB9FrvFkAYwbctin6g47O98s3Win7Ewhy0w8JaiUA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-minify/binding-linux-arm-gnueabihf": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.87.0.tgz", + "integrity": "sha512-NjbGXnNaAl5EgyonaDg2cPyH2pTf5a/+AP/5SRCJ0KetpXV22ZSUCvcy04Yt4QqjMcDs+WnJaGVxwx15Ofr6Gw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-minify/binding-linux-arm-musleabihf": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.87.0.tgz", + "integrity": "sha512-llAjfCA0iV2LMMl+LTR3JhqAc2iQmj+DTKd0VWOrbNOuNczeE9D5kJFkqYplD73LrkuqxrX9oDeUjjeLdVBPXw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-minify/binding-linux-arm64-gnu": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.87.0.tgz", + "integrity": "sha512-tf2Shom09AaSmu7U1hYYcEFF/cd+20HtmQ8eyGsRkqD5bqUj6lDu8TNSU9FWZ9tcZ83NzyFMwXZWHyeeIIbpxw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-minify/binding-linux-arm64-musl": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.87.0.tgz", + "integrity": "sha512-pgWeYfSprtpnJVea9Q5eI6Eo80lDGlMw2JdcSMXmShtBjEhBl6bvDNHlV+6kNfh7iT65y/uC6FR8utFrRghu8A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-minify/binding-linux-riscv64-gnu": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.87.0.tgz", + "integrity": "sha512-O1QPczlT+lqNZVeKOdFxxL+s1RIlnixaJYFLrcqDcRyn82MGKLz7sAenBTFRQoIfLnSxtMGL6dqHOefYkQx7Cg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-minify/binding-linux-s390x-gnu": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.87.0.tgz", + "integrity": "sha512-tcwt3ZUWOKfNLXN2edxFVHMlIuPvbuyMaKmRopgljSCfFcNHWhfTNlxlvmECRNhuQ91EcGwte6F1dwoeMCNd7A==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-minify/binding-linux-x64-gnu": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.87.0.tgz", + "integrity": "sha512-Xf4AXF14KXUzSnfgTcFLFSM0TykJhFw14+xwNvlAb6WdqXAKlMrz9joIAezc8dkW1NNscCVTsqBUPJ4RhvCM1Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-minify/binding-linux-x64-musl": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-x64-musl/-/binding-linux-x64-musl-0.87.0.tgz", + "integrity": "sha512-LIqvpx9UihEW4n9QbEljDnfUdAWqhr6dRqmzSFwVAeLZRUECluLCDdsdwemrC/aZkvnisA4w0LFcFr3HmeTLJg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-minify/binding-wasm32-wasi": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-wasm32-wasi/-/binding-wasm32-wasi-0.87.0.tgz", + "integrity": "sha512-h0xluvc+YryfH5G5dndjGHuA/D4Kp85EkPMxqoOjNudOKDCtdobEaC9horhCqnOOQ0lgn+PGFl3w8u4ToOuRrA==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-minify/binding-win32-arm64-msvc": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.87.0.tgz", + "integrity": "sha512-fgxSx+TUc7e2rNtRAMnhHrjqh1e8p/JKmWxRZXtkILveMr/TOHGiDis7U3JJbwycmTZ+HSsJ/PNFQl+tKzmDxw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-minify/binding-win32-x64-msvc": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.87.0.tgz", + "integrity": "sha512-K6TTrlitEJgD0FGIW2r0t3CIJNqBkzHT97h49gZLS24ey2UG1zKt27iSHkpXMJYDiG97ZD2yv3pSph1ctMlFXw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.87.0.tgz", + "integrity": "sha512-3APxTyYaAjpW5zifjzfsPgoIa4YHwA5GBjtgLRQpGVXCykXBIEbUTokoAs411ZuOwS3sdTVXBTGAdziXRd8rUg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.87.0.tgz", + "integrity": "sha512-99e8E76M+k3Gtwvs5EU3VTs2hQkJmvnrl/eu7HkBUc9jLFHA4nVjYSgukMuqahWe270udUYEPRfcWKmoE1Nukg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.87.0.tgz", + "integrity": "sha512-2rRo6Dz560/4ot5Q0KPUTEunEObkP8mDC9mMiH0RJk1FiOb9c+xpPbkYoUHNKuVMm8uIoiBCxIAbPtBhs9QaXQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.87.0.tgz", + "integrity": "sha512-uR+WZAvWkFQPVoeqXgQFr7iy+3hEI295qTbQ4ujmklgM5eTX3YgMFoIV00Stloxfd1irSDDSaK7ySnnzF6mRJg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.87.0.tgz", + "integrity": "sha512-Emm1NpVGKbwzQOIZJI8ZuZu0z8FAd5xscqdS6qpDFpDdEMxk6ab7o3nM8V09RhNCORAzeUlk4TBHQ2Crzjd50A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.87.0.tgz", + "integrity": "sha512-1PPCxRZSJXzQaqc8y+wH7EqPgSfQ/JU3pK6WTN/1SUe/8paNVSKKqk175a8BbRVxGUtPnwEG89pi+xfPTSE7GA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.87.0.tgz", + "integrity": "sha512-fcnnsfcyLamJOMVKq+BQ8dasb8gRnZtNpCUfZhaEFAdXQ7J2RmZreFzlygcn80iti0V7c5LejcjHbF4IdK3GAw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.87.0.tgz", + "integrity": "sha512-tBPkSPgRSSbmrje8CUovISi/Hj/tWjZJ3n/qnrjx2B+u86hWtwLsngtPDQa5d4seSyDaHSx6tNEUcH7+g5Ee0Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.87.0.tgz", + "integrity": "sha512-z4UKGM4wv2wEAQAlx2pBq6+pDJw5J/5oDEXqW6yBSLbWLjLDo4oagmRSE3+giOWteUa+0FVJ+ypq4iYxBkYSWg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.87.0.tgz", + "integrity": "sha512-6W1ENe/nZtr2TBnrEzmdGEraEAdZOiH3YoUNNeQWuqwLkmpoHTJJdclieToPe/l2IKJ4WL3FsSLSGHE8yt/OEg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.87.0.tgz", + "integrity": "sha512-s3kB/Ii3X3IOZ27Iu7wx2zYkIcDO22Emu32SNC6kkUSy09dPBc1yaW14TnAkPMe/rvtuzR512JPWj3iGpl+Dng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.87.0.tgz", + "integrity": "sha512-3+M9hfrZSDi4+Uy4Ll3rtOuVG3IHDQlj027jgtmAAHJK1eqp4CQfC7rrwE+LFUqUwX+KD2GwlxR+eHyyEf5Gbg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.87.0.tgz", + "integrity": "sha512-2jgeEeOa4GbQQg2Et/gFTgs5wKS/+CxIg+CN2mMOJ4EqbmvUVeGiumO01oFOWTYnJy1oONwIocBzrnMuvOcItA==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.87.0.tgz", + "integrity": "sha512-KZp9poaBaVvuFM0TrsHCDOjPQK5eMDXblz21boMhKHGW5/bOlkMlg3CYn5j0f67FkK68NSdNKREMxmibBeXllQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.87.0.tgz", + "integrity": "sha512-86uisngtp/8XdcerIKxMyJTqgDSTJatkfpylpUH0d96W8Bb9E+bVvM2fIIhLWB0Eb03PeY2BdIT7DNIln9TnHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.87.0.tgz", + "integrity": "sha512-ipZFWVGE9fADBVXXWJWY/cxpysc41Gt5upKDeb32F6WMgFyO7XETUMVq8UuREKCih+Km5E6p2VhEvf6Fuhey6g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxc-transform/binding-android-arm64": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-android-arm64/-/binding-android-arm64-0.87.0.tgz", + "integrity": "sha512-B7W6J8T9cS054LUGLfYkYz8bz5+t+4yPftZ67Bn6MJ03okMLnbbEfm1bID1tqcP5tJwMurTILVy/dQfDYDcMgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-transform/binding-darwin-arm64": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-darwin-arm64/-/binding-darwin-arm64-0.87.0.tgz", + "integrity": "sha512-HImW3xOPx7FHKqfC5WfE82onhRfnWQUiB7R+JgYrk+7NR404h3zANSPzu3V/W9lbDxlmHTcqoD2LKbNC5j0TQA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-transform/binding-darwin-x64": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-darwin-x64/-/binding-darwin-x64-0.87.0.tgz", + "integrity": "sha512-MDbgugi6mvuPTfS78E2jyozm7493Kuqmpc5r406CsUdEsXlnsF+xvmKlrW9ZIkisO74dD+HWouSiDtNyPQHjlw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-transform/binding-freebsd-x64": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-freebsd-x64/-/binding-freebsd-x64-0.87.0.tgz", + "integrity": "sha512-N0M5D/4haJw7BMn2WZ3CWz0WkdLyoK1+3KxOyCv2CPedMCxx6eQay2AtJxSzj9tjVU1+ukbSb2fDO24JIJGsVA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-transform/binding-linux-arm-gnueabihf": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.87.0.tgz", + "integrity": "sha512-PubObCNOUOzm1S+P0yn7S+/6xRLbSPMqhgrb73L3p+J1Z20fv/FYVg0kFd36Yho24TSC/byOkebEZWAtxCasWw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-transform/binding-linux-arm-musleabihf": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.87.0.tgz", + "integrity": "sha512-Nk2d/FS7sMCmCl99vHojzigakjDPamkjOXs2i+H71o/NqytS0pk3M+tXat8M3IGpeLJIEszA5Mv+dcq731nlYA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-transform/binding-linux-arm64-gnu": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.87.0.tgz", + "integrity": "sha512-BxFkIcso2V1+FCDoU+KctxvJzSQVSnEZ5EEQ8O3Up9EoFVQRnZ8ktXvqYj2Oqvc4IYPskLPsKUgc9gdK8wGhUg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-transform/binding-linux-arm64-musl": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.87.0.tgz", + "integrity": "sha512-MZ1/TNaebhXK73j1UDfwyBFnAy0tT3n6otOkhlt1vlJwqboUS/D7E/XrCZmAuHIfVPxAXRPovkl7kfxLB43SKw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-transform/binding-linux-riscv64-gnu": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.87.0.tgz", + "integrity": "sha512-JCWE6n4Hicu0FVbvmLdH/dS8V6JykOUsbrbDYm6JwFlHr4eFTTlS2B+mh5KPOxcdeOlv/D/XRnvMJ6WGYs25EA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-transform/binding-linux-s390x-gnu": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.87.0.tgz", + "integrity": "sha512-n2NTgM+3PqFagJV9UXRDNOmYesF+TO9SF9FeHqwVmW893ayef9KK+vfWAAhvOYHXYaKWT5XoHd87ODD7nruyhw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-transform/binding-linux-x64-gnu": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.87.0.tgz", + "integrity": "sha512-ZOKW3wx0bW2O7jGdOzr8DyLZqX2C36sXvJdsHj3IueZZ//d/NjLZqEiUKz+q0JlERHtCVKShQ5PLaCx7NpuqNg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-transform/binding-linux-x64-musl": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-x64-musl/-/binding-linux-x64-musl-0.87.0.tgz", + "integrity": "sha512-eIspx/JqkVMPK1CAYEOo2J8o49s4ZTf+32MSMUknIN2ZS1fvRmWS0D/xFFaLP/9UGhdrXRIPbn/iSYEA8JnV/g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-transform/binding-wasm32-wasi": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-wasm32-wasi/-/binding-wasm32-wasi-0.87.0.tgz", + "integrity": "sha512-4uRjJQnt/+kmJUIC6Iwzn+MqqZhLP1zInPtDwgL37KI4VuUewUQWoL+sggMssMEgm7ZJwOPoZ6piuSWwMgOqgQ==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-transform/binding-win32-arm64-msvc": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.87.0.tgz", + "integrity": "sha512-l/qSi4/N5W1yXKU9+1gWGo0tBoRpp4zvHYrpsbq3zbefPL4VYdA0gKF7O10/ZQVkYylzxiVh2zpYO34/FbZdIg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-transform/binding-win32-x64-msvc": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.87.0.tgz", + "integrity": "sha512-jG/MhMjfSdyj5KyhnwNWr4mnAlAsz+gNUYpjQ+UXWsfsoB3f8HqbsTkG02RBtNa/IuVQYvYYVf1eIimNN3gBEQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-wasm": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-wasm/-/watcher-wasm-2.5.1.tgz", + "integrity": "sha512-RJxlQQLkaMMIuWRozy+z2vEqbaQlCuaCgVZIUCzQLYggY22LZbP5Y1+ia+FD724Ids9e+XIyOLXLrLgQSHIthw==", + "bundleDependencies": [ + "napi-wasm" + ], + "license": "MIT", + "dependencies": { + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "napi-wasm": "^1.1.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-wasm/node_modules/napi-wasm": { + "version": "1.1.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "license": "MIT" + }, + "node_modules/@poppinss/colors": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.5.tgz", + "integrity": "sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==", + "license": "MIT", + "dependencies": { + "kleur": "^4.1.5" + } + }, + "node_modules/@poppinss/colors/node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@poppinss/dumper": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.4.tgz", + "integrity": "sha512-iG0TIdqv8xJ3Lt9O8DrPRxw1MRLjNpoqiSGU03P/wNLP/s0ra0udPJ1J2Tx5M0J3H/cVyEgpbn8xUKRY9j59kQ==", + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + } + }, + "node_modules/@poppinss/exception": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.2.tgz", + "integrity": "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==", + "license": "MIT" + }, + "node_modules/@resvg/resvg-js": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.6.2.tgz", + "integrity": "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@resvg/resvg-js-android-arm-eabi": "2.6.2", + "@resvg/resvg-js-android-arm64": "2.6.2", + "@resvg/resvg-js-darwin-arm64": "2.6.2", + "@resvg/resvg-js-darwin-x64": "2.6.2", + "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2", + "@resvg/resvg-js-linux-arm64-gnu": "2.6.2", + "@resvg/resvg-js-linux-arm64-musl": "2.6.2", + "@resvg/resvg-js-linux-x64-gnu": "2.6.2", + "@resvg/resvg-js-linux-x64-musl": "2.6.2", + "@resvg/resvg-js-win32-arm64-msvc": "2.6.2", + "@resvg/resvg-js-win32-ia32-msvc": "2.6.2", + "@resvg/resvg-js-win32-x64-msvc": "2.6.2" + } + }, + "node_modules/@resvg/resvg-js-android-arm-eabi": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.2.tgz", + "integrity": "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-android-arm64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.6.2.tgz", + "integrity": "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-darwin-arm64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.6.2.tgz", + "integrity": "sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-darwin-x64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.6.2.tgz", + "integrity": "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm-gnueabihf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.6.2.tgz", + "integrity": "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm64-gnu": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.6.2.tgz", + "integrity": "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm64-musl": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.6.2.tgz", + "integrity": "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-x64-gnu": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.6.2.tgz", + "integrity": "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-x64-musl": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.6.2.tgz", + "integrity": "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-arm64-msvc": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.6.2.tgz", + "integrity": "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-ia32-msvc": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.6.2.tgz", + "integrity": "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-x64-msvc": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.6.2.tgz", + "integrity": "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-wasm": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@resvg/resvg-wasm/-/resvg-wasm-2.6.2.tgz", + "integrity": "sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.29", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz", + "integrity": "sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==", + "license": "MIT" + }, + "node_modules/@rollup/plugin-alias": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-5.1.1.tgz", + "integrity": "sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "28.0.6", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.6.tgz", + "integrity": "sha512-XSQB1K7FUU5QP+3lOQmVCE3I0FcbbNvmNT4VJSj93iUjayaARrTQeoRdiYQoftAJBLrR9t2agwAd3ekaTgHNlw==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@rollup/plugin-inject": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz", + "integrity": "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-inject/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", + "integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz", + "integrity": "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "license": "MIT", + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.2.tgz", + "integrity": "sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.2.tgz", + "integrity": "sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.2.tgz", + "integrity": "sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.2.tgz", + "integrity": "sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.2.tgz", + "integrity": "sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.2.tgz", + "integrity": "sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.2.tgz", + "integrity": "sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.2.tgz", + "integrity": "sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.2.tgz", + "integrity": "sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.2.tgz", + "integrity": "sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.50.2.tgz", + "integrity": "sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.2.tgz", + "integrity": "sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.2.tgz", + "integrity": "sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.2.tgz", + "integrity": "sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.2.tgz", + "integrity": "sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.2.tgz", + "integrity": "sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.2.tgz", + "integrity": "sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.2.tgz", + "integrity": "sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.2.tgz", + "integrity": "sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.2.tgz", + "integrity": "sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.2.tgz", + "integrity": "sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@shuding/opentype.js": { + "version": "1.4.0-beta.0", + "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz", + "integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fflate": "^0.7.3", + "string.prototype.codepointat": "^0.2.1" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.0.tgz", + "integrity": "sha512-7F/yz2IphV39hiS2zB4QYVkivrptHHh0K8qJJd9HhuWSdvf8AN7NpebW3CcDZDBQsUPMoDKWsY2WWgW7bqOcfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@speed-highlight/core": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.7.tgz", + "integrity": "sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g==", + "license": "CC0-1.0" + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.17.tgz", + "integrity": "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/parse-path": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/parse-path/-/parse-path-7.0.3.tgz", + "integrity": "sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==", + "license": "MIT" + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@unhead/addons": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/@unhead/addons/-/addons-1.11.20.tgz", + "integrity": "sha512-6iZd9dFv0pcp3gIuS32OPbA7BUgTixTKnkFzqU2OYbrVDqHrY8BEuUOZY9BN83edVSp376y7xIp1qucmL2es8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.4", + "@unhead/schema": "1.11.20", + "@unhead/shared": "1.11.20", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17", + "mlly": "^1.7.3", + "ufo": "^1.5.4", + "unplugin": "^2.1.2", + "unplugin-ast": "^0.13.1" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@unhead/dom": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.11.20.tgz", + "integrity": "sha512-jgfGYdOH+xHJF/j8gudjsYu3oIjFyXhCWcgKaw3vQnT616gSqyqnGQGOItL+BQtQZACKNISwIfx5PuOtztMKLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@unhead/schema": "1.11.20", + "@unhead/shared": "1.11.20" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@unhead/schema": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.11.20.tgz", + "integrity": "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookable": "^5.5.3", + "zhead": "^2.2.4" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@unhead/schema-org": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/@unhead/schema-org/-/schema-org-1.11.20.tgz", + "integrity": "sha512-ElyhQW0+w4xPB1Im/XWvdUmW+abGdgrsrpXnQZ7uILtPlLBoK1IUC8miFCj1//bh9tHYRB/OfEyBODunvE1woA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@unhead/schema": "1.11.20", + "@unhead/shared": "1.11.20", + "@unhead/vue": "1.11.20", + "defu": "^6.1.4", + "ohash": "^1.1.4", + "ufo": "^1.5.4", + "unhead": "1.11.20" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "@unhead/vue": "^1.11.14", + "unhead": "^1.11.14" + }, + "peerDependenciesMeta": { + "@unhead/vue": { + "optional": true + } + } + }, + "node_modules/@unhead/schema-org/node_modules/@unhead/vue": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-1.11.20.tgz", + "integrity": "sha512-sqQaLbwqY9TvLEGeq8Fd7+F2TIuV3nZ5ihVISHjWpAM3y7DwNWRU7NmT9+yYT+2/jw1Vjwdkv5/HvDnvCLrgmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@unhead/schema": "1.11.20", + "@unhead/shared": "1.11.20", + "hookable": "^5.5.3", + "unhead": "1.11.20" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "vue": ">=2.7 || >=3" + } + }, + "node_modules/@unhead/schema-org/node_modules/ohash": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.6.tgz", + "integrity": "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@unhead/schema-org/node_modules/unhead": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/unhead/-/unhead-1.11.20.tgz", + "integrity": "sha512-3AsNQC0pjwlLqEYHLjtichGWankK8yqmocReITecmpB1H0aOabeESueyy+8X1gyJx4ftZVwo9hqQ4O3fPWffCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@unhead/dom": "1.11.20", + "@unhead/schema": "1.11.20", + "@unhead/shared": "1.11.20", + "hookable": "^5.5.3" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@unhead/shared": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.11.20.tgz", + "integrity": "sha512-1MOrBkGgkUXS+sOKz/DBh4U20DNoITlJwpmvSInxEUNhghSNb56S0RnaHRq0iHkhrO/cDgz2zvfdlRpoPLGI3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@unhead/schema": "1.11.20", + "packrup": "^0.1.2" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@unhead/ssr": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/@unhead/ssr/-/ssr-1.11.20.tgz", + "integrity": "sha512-j6ehzmdWGAvv0TEZyLE3WBnG1ULnsbKQcLqBDh3fvKS6b3xutcVZB7mjvrVE7ckSZt6WwOtG0ED3NJDS7IjzBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@unhead/schema": "1.11.20", + "@unhead/shared": "1.11.20" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@unhead/vue": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-2.0.17.tgz", + "integrity": "sha512-jzmGZYeMAhETV6qfetmLbZzUjjx1TjdNvFSobeFZb73D7dwD9wl/nOAx36qq+TvjZsLJdF5PQWToz2oDGAUqCg==", + "license": "MIT", + "dependencies": { + "hookable": "^5.5.3", + "unhead": "2.0.17" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "vue": ">=3.5.18" + } + }, + "node_modules/@unocss/core": { + "version": "66.5.1", + "resolved": "https://registry.npmjs.org/@unocss/core/-/core-66.5.1.tgz", + "integrity": "sha512-BUgN87sUIffco1d+1IuV4a1gKTI1YAFa7CTjxglLUAnopXPPJ+Q77G10zoBoFLzutiIOYLsesa3hzbQvDhosnA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@unocss/extractor-arbitrary-variants": { + "version": "66.5.1", + "resolved": "https://registry.npmjs.org/@unocss/extractor-arbitrary-variants/-/extractor-arbitrary-variants-66.5.1.tgz", + "integrity": "sha512-SpI2uv6bWyPyY3Tv7CxsFnHBjSTlNRcPCnfvD8gSKbAt7R+RqV0nrdkv7wSW+Woc5TYl8PClLEFSBIvo0c1h9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@unocss/core": "66.5.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@unocss/preset-mini": { + "version": "66.5.1", + "resolved": "https://registry.npmjs.org/@unocss/preset-mini/-/preset-mini-66.5.1.tgz", + "integrity": "sha512-kBEbA0kEXRtoHQ98o4b6f9sp1u5BanPzi+GMnWdmOWvbLAiLw1vcgXGPTX3sO+gzIMrwu0Famw6xiztWzAFjWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@unocss/core": "66.5.1", + "@unocss/extractor-arbitrary-variants": "66.5.1", + "@unocss/rule-utils": "66.5.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@unocss/preset-wind3": { + "version": "66.5.1", + "resolved": "https://registry.npmjs.org/@unocss/preset-wind3/-/preset-wind3-66.5.1.tgz", + "integrity": "sha512-L1yMmKpwUWYUnScQq5jMTGvfMy/GBqVj40VS5afyOlzWnBeSkc/y4AxeW/khzGwqE/QaFcLWXiXwQVJIyxN02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@unocss/core": "66.5.1", + "@unocss/preset-mini": "66.5.1", + "@unocss/rule-utils": "66.5.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@unocss/rule-utils": { + "version": "66.5.1", + "resolved": "https://registry.npmjs.org/@unocss/rule-utils/-/rule-utils-66.5.1.tgz", + "integrity": "sha512-GuBKHrDv3bdq5N1HfOr1tD864vI1EIiovBVJSfg7x9ERA4jJSnyMpGk/hbLuDIXF25EnVdZ1lFhEpJgur9+9sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@unocss/core": "^66.5.1", + "magic-string": "^0.30.18" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vercel/nft": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.30.1.tgz", + "integrity": "sha512-2mgJZv4AYBFkD/nJ4QmiX5Ymxi+AisPLPcS/KPXVqniyQNqKXX+wjieAbDXQP3HcogfEbpHoRMs49Cd4pfkk8g==", + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^2.0.0", + "@rollup/pluginutils": "^5.1.3", + "acorn": "^8.6.0", + "acorn-import-attributes": "^1.9.5", + "async-sema": "^3.1.1", + "bindings": "^1.4.0", + "estree-walker": "2.0.2", + "glob": "^10.4.5", + "graceful-fs": "^4.2.9", + "node-gyp-build": "^4.2.2", + "picomatch": "^4.0.2", + "resolve-from": "^5.0.0" + }, + "bin": { + "nft": "out/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/nft/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz", + "integrity": "sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==", + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.29" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitejs/plugin-vue-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-5.1.1.tgz", + "integrity": "sha512-uQkfxzlF8SGHJJVH966lFTdjM/lGcwJGzwAHpVqAPDD/QcsqoUGa+q31ox1BrUfi+FLP2ChVp7uLXE3DkHyDdQ==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.3", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.0", + "@rolldown/pluginutils": "^1.0.0-beta.34", + "@vue/babel-plugin-jsx": "^1.5.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", + "vue": "^3.0.0" + } + }, + "node_modules/@vitejs/plugin-vue-jsx/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz", + "integrity": "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==", + "license": "MIT" + }, + "node_modules/@volar/language-core": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.23.tgz", + "integrity": "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==", + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.23" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.23.tgz", + "integrity": "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==", + "license": "MIT" + }, + "node_modules/@vue-macros/common": { + "version": "3.0.0-beta.16", + "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.0.0-beta.16.tgz", + "integrity": "sha512-8O2gWxWFiaoNkk7PGi0+p7NPGe/f8xJ3/INUufvje/RZOs7sJvlI1jnR4lydtRFa/mU0ylMXUXXjSK0fHDEYTA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-sfc": "^3.5.17", + "ast-kit": "^2.1.1", + "local-pkg": "^1.1.1", + "magic-string-ast": "^1.0.0", + "unplugin-utils": "^0.2.4" + }, + "engines": { + "node": ">=20.18.0" + }, + "funding": { + "url": "https://github.com/sponsors/vue-macros" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.2.25" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, + "node_modules/@vue-macros/common/node_modules/ast-kit": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.1.2.tgz", + "integrity": "sha512-cl76xfBQM6pztbrFWRnxbrDm9EOqDr1BF6+qQnnDZG2Co2LjyUktkN9GTJfBAfdae+DbT2nJf2nCGAdDDN7W2g==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.0", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=20.18.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/@vue-macros/common/node_modules/magic-string-ast": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.2.tgz", + "integrity": "sha512-8ngQgLhcT0t3YBdn9CGkZqCYlvwW9pm7aWJwd7AxseVWf1RU8ZHCQvG1mt3N5vvUme+pXTcHB8G/7fE666U8Vw==", + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17" + }, + "engines": { + "node": ">=20.18.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.5.0.tgz", + "integrity": "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==", + "license": "MIT" + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.5.0.tgz", + "integrity": "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.2", + "@vue/babel-helper-vue-transform-on": "1.5.0", + "@vue/babel-plugin-resolve-type": "1.5.0", + "@vue/shared": "^3.5.18" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.5.0.tgz", + "integrity": "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/parser": "^7.28.0", + "@vue/compiler-sfc": "^3.5.18" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.21.tgz", + "integrity": "sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@vue/shared": "3.5.21", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.21.tgz", + "integrity": "sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.21", + "@vue/shared": "3.5.21" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.21.tgz", + "integrity": "sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@vue/compiler-core": "3.5.21", + "@vue/compiler-dom": "3.5.21", + "@vue/compiler-ssr": "3.5.21", + "@vue/shared": "3.5.21", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.18", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.21.tgz", + "integrity": "sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.21", + "@vue/shared": "3.5.21" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/devtools-core": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.7.7.tgz", + "integrity": "sha512-9z9TLbfC+AjAi1PQyWX+OErjIaJmdFlbDHcD+cAMYKY6Bh5VlsAtCeGyRMrXwIlMEQPukvnWt3gZBLwTAIMKzQ==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.7", + "@vue/devtools-shared": "^7.7.7", + "mitt": "^3.0.1", + "nanoid": "^5.1.0", + "pathe": "^2.0.3", + "vite-hot-client": "^2.0.4" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz", + "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.7", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-kit/node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz", + "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.0.7.tgz", + "integrity": "sha512-0sqqyqJ0Gn33JH3TdIsZLCZZ8Gr4kwlg8iYOnOrDDkJKSjFurlQY/bEFQx5zs7SX2C/bjMkmPYq/NiyY1fTOkw==", + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.23", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^2.0.5", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.2" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.21.tgz", + "integrity": "sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.21" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.21.tgz", + "integrity": "sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.21", + "@vue/shared": "3.5.21" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.21.tgz", + "integrity": "sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.21", + "@vue/runtime-core": "3.5.21", + "@vue/shared": "3.5.21", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.21.tgz", + "integrity": "sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.21", + "@vue/shared": "3.5.21" + }, + "peerDependencies": { + "vue": "3.5.21" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.21.tgz", + "integrity": "sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.9.0.tgz", + "integrity": "sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "13.9.0", + "@vueuse/shared": "13.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.9.0.tgz", + "integrity": "sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/motion": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@vueuse/motion/-/motion-2.2.6.tgz", + "integrity": "sha512-gKFktPtrdypSv44SaW1oBJKLBiP6kE5NcoQ6RsAU3InemESdiAutgQncfPe/rhLSLCtL4jTAhMmFfxoR6gm5LQ==", + "license": "MIT", + "dependencies": { + "@vueuse/core": "^10.10.0", + "@vueuse/shared": "^10.10.0", + "csstype": "^3.1.3", + "framesync": "^6.1.2", + "popmotion": "^11.0.5", + "style-value-types": "^5.1.2" + }, + "optionalDependencies": { + "@nuxt/kit": "^3.13.0" + }, + "peerDependencies": { + "vue": ">=3.0.0" + } + }, + "node_modules/@vueuse/motion/node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/@vueuse/motion/node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/motion/node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/motion/node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/motion/node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/motion/node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/nuxt": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/nuxt/-/nuxt-11.3.0.tgz", + "integrity": "sha512-FxtRTgFmsoASamR3lOftv/r11o1BojF9zir8obbTnKamVZdlQ5rgJ0hHgVbrgA6dlMuEx/PzwqAmiKNFdU4oCQ==", + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.14.1592", + "@vueuse/core": "11.3.0", + "@vueuse/metadata": "11.3.0", + "local-pkg": "^0.5.1", + "vue-demi": ">=0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "nuxt": "^3.0.0" + } + }, + "node_modules/@vueuse/nuxt/node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/@vueuse/nuxt/node_modules/@vueuse/core": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-11.3.0.tgz", + "integrity": "sha512-7OC4Rl1f9G8IT6rUfi9JrKiXy4bfmHhZ5x2Ceojy0jnd3mHNEvV4JaRygH362ror6/NZ+Nl+n13LPzGiPN8cKA==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "11.3.0", + "@vueuse/shared": "11.3.0", + "vue-demi": ">=0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/nuxt/node_modules/@vueuse/metadata": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-11.3.0.tgz", + "integrity": "sha512-pwDnDspTqtTo2HwfLw4Rp6yywuuBdYnPYDq+mO38ZYKGebCUQC/nVj/PXSiK9HX5otxLz8Fn7ECPbjiRz2CC3g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/nuxt/node_modules/@vueuse/shared": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-11.3.0.tgz", + "integrity": "sha512-P8gSSWQeucH5821ek2mn/ciCk+MS/zoRKqdQIM3bHq6p7GXDAJLmnRRKmF5F65sAVJIfzQlwR3aDzwCn10s8hA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/nuxt/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/@vueuse/nuxt/node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/nuxt/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/@vueuse/nuxt/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/shared": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.9.0.tgz", + "integrity": "sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/alien-signals": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-2.0.7.tgz", + "integrity": "sha512-wE7y3jmYeb0+h6mr5BOovuqhFv22O/MV9j5p0ndJsa7z1zJNPGQ4ph5pQk/kTTCWRC3xsA4SmtwmkzQO+7NCNg==", + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.1.0.tgz", + "integrity": "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ast-kit": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-1.4.3.tgz", + "integrity": "sha512-MdJqjpodkS5J149zN0Po+HPshkTdUyrvF7CKTafUgv69vBSPtncrj+3IiUgqdd7ElIEkbeXCsEouBUwLrw9Ilg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.0", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=16.14.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/ast-walker-scope": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.8.2.tgz", + "integrity": "sha512-3pYeLyDZ6nJew9QeBhS4Nly02269Dkdk32+zdbbKmL6n4ZuaGorwwA+xx12xgOciA8BF1w9x+dlH7oUkFTW91w==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "ast-kit": "^2.1.2" + }, + "engines": { + "node": ">=20.18.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/ast-walker-scope/node_modules/ast-kit": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.1.2.tgz", + "integrity": "sha512-cl76xfBQM6pztbrFWRnxbrDm9EOqDr1BF6+qQnnDZG2Co2LjyUktkN9GTJfBAfdae+DbT2nJf2nCGAdDDN7W2g==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.0", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=20.18.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/async-sema": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/async-sema/-/async-sema-3.1.1.tgz", + "integrity": "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==", + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/b4a": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.1.tgz", + "integrity": "sha512-ZovbrBV0g6JxK5cGUF1Suby1vLfKjv4RWi8IxoaO/Mon8BDD9I21RxjHFtgQ+kskJqLAVyQZly3uMBui+vhc8Q==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/bare-events": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.7.0.tgz", + "integrity": "sha512-b3N5eTW1g7vXkw+0CXh/HazGTcO5KYuu/RCNaJbDMPI6LHDi+7qe8EmxKUVe1sUbY2KZOVZFyj62x0OEz9qyAA==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.4.4.tgz", + "integrity": "sha512-Q8yxM1eLhJfuM7KXVP3zjhBvtMJCYRByoTT+wHXjpdMELv0xICFJX+1w4c7csa+WZEOsq4ItJ4RGwvzid6m/dw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", + "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.2.2.tgz", + "integrity": "sha512-g+ueNGKkrjMazDG3elZO1pNs3HY5+mMmOet1jtKyhOaCnkLzitxf26z7hoAEkDNgdNmnc1KIlt/dw6Po6xZMpA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz", + "integrity": "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/birpc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.5.0.tgz", + "integrity": "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.26.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", + "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c12": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.0.tgz", + "integrity": "sha512-K9ZkuyeJQeqLEyqldbYLG3wjqwpw4BVaAqvmxq3GYKK0b1A/yYQdIcJxkzAOWcNVWhJpRXAPfZFueekiY/L8Dw==", + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^17.2.2", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.5.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.0.0", + "pkg-types": "^2.3.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/cache-content-type/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cache-content-type/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001743", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz", + "integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chrome-launcher": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.2.0.tgz", + "integrity": "sha512-JbuGuBNss258bvGil7FT4HKdC3SC2K7UAEUqiPy3ACS3Yxo3hAW6bvFpCu2HsIJLgTqxgEX6BkujvzZfLpUD0Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^2.0.1" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.cjs" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/chrome-launcher/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chrome-launcher/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chrome-launcher/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/clipboardy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-4.0.0.tgz", + "integrity": "sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==", + "license": "MIT", + "dependencies": { + "execa": "^8.0.1", + "is-wsl": "^3.1.0", + "is64bit": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "license": "MIT" + }, + "node_modules/compatx": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/compatx/-/compatx-0.2.0.tgz", + "integrity": "sha512-6gLRNt4ygsi5NyMVhceOCFv14CIdDFN7fQjX1U4+47qVE/+kjPoXMK65KWK+dWxmFzMTuKazoQ9sch6pM0p5oA==", + "license": "MIT" + }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/compress-commons/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cookie-es": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", + "license": "MIT" + }, + "node_modules/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/croner": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/croner/-/croner-9.1.0.tgz", + "integrity": "sha512-p9nwwR4qyT5W996vBZhdvBCnMhicY5ytZkR4D1Xj0wuTDEiMnjwR57Q3RXYY/s0EpX6Ay3vgIcfaR+ewGHsi+g==", + "license": "MIT", + "engines": { + "node": ">=18.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/css-background-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz", + "integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/css-box-shadow": { + "version": "1.0.0-3", + "resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz", + "integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==", + "dev": true, + "license": "MIT" + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-declaration-sorter": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", + "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", + "license": "ISC", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-gradient-parser": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/css-gradient-parser/-/css-gradient-parser-0.0.16.tgz", + "integrity": "sha512-3O5QdqgFRUbXvK1x5INf1YkBz1UKSWqrd63vWsum8MNHDBYD5urm3QtxZbKU259OrEXNM26lP/MPY3d1IGkBgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssfilter": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", + "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/cssnano": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.1.tgz", + "integrity": "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ==", + "license": "MIT", + "dependencies": { + "cssnano-preset-default": "^7.0.9", + "lilconfig": "^3.1.3" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/cssnano-preset-default": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.9.tgz", + "integrity": "sha512-tCD6AAFgYBOVpMBX41KjbvRh9c2uUjLXRyV7KHSIrwHiq5Z9o0TFfUCoM3TwVrRsRteN3sVXGNvjVNxYzkpTsA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.1", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^5.0.1", + "postcss-calc": "^10.1.1", + "postcss-colormin": "^7.0.4", + "postcss-convert-values": "^7.0.7", + "postcss-discard-comments": "^7.0.4", + "postcss-discard-duplicates": "^7.0.2", + "postcss-discard-empty": "^7.0.1", + "postcss-discard-overridden": "^7.0.1", + "postcss-merge-longhand": "^7.0.5", + "postcss-merge-rules": "^7.0.6", + "postcss-minify-font-values": "^7.0.1", + "postcss-minify-gradients": "^7.0.1", + "postcss-minify-params": "^7.0.4", + "postcss-minify-selectors": "^7.0.5", + "postcss-normalize-charset": "^7.0.1", + "postcss-normalize-display-values": "^7.0.1", + "postcss-normalize-positions": "^7.0.1", + "postcss-normalize-repeat-style": "^7.0.1", + "postcss-normalize-string": "^7.0.1", + "postcss-normalize-timing-functions": "^7.0.1", + "postcss-normalize-unicode": "^7.0.4", + "postcss-normalize-url": "^7.0.1", + "postcss-normalize-whitespace": "^7.0.1", + "postcss-ordered-values": "^7.0.2", + "postcss-reduce-initial": "^7.0.4", + "postcss-reduce-transforms": "^7.0.1", + "postcss-svgo": "^7.1.0", + "postcss-unique-selectors": "^7.0.4" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/cssnano-utils": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.1.tgz", + "integrity": "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==", + "license": "MIT", + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/db0": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/db0/-/db0-0.3.2.tgz", + "integrity": "sha512-xzWNQ6jk/+NtdfLyXEipbX55dmDSeteLFt/ayF+wZUU5bzKgmrDOxmInUTbyVRp46YwnJdkDA1KhB7WIXFofJw==", + "license": "MIT", + "peerDependencies": { + "@electric-sql/pglite": "*", + "@libsql/client": "*", + "better-sqlite3": "*", + "drizzle-orm": "*", + "mysql2": "*", + "sqlite3": "*" + }, + "peerDependenciesMeta": { + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "drizzle-orm": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/devalue": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.3.2.tgz", + "integrity": "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==", + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-prop": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", + "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^4.18.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dotenv": { + "version": "17.2.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", + "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.221", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.221.tgz", + "integrity": "sha512-/1hFJ39wkW01ogqSyYoA4goOXOtMRy6B+yvA1u42nnsEGtHzIzmk93aPISumVQeblj47JUHLC9coCjUxb1EvtQ==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz", + "integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/errx": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/errx/-/errx-0.1.0.tgz", + "integrity": "sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==", + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "license": "MIT" + }, + "node_modules/externality": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/externality/-/externality-1.0.2.tgz", + "integrity": "sha512-LyExtJWKxtgVzmgtEHyQtLFpw1KFhQphF9nTG8TpAIVkiI/xQ3FJh75tRFLYl4hkn7BNIIdLJInuDAavX35pMw==", + "license": "MIT", + "dependencies": { + "enhanced-resolve": "^5.14.1", + "mlly": "^1.3.0", + "pathe": "^1.1.1", + "ufo": "^1.1.2" + } + }, + "node_modules/externality/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-npm-meta": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/fast-npm-meta/-/fast-npm-meta-0.4.6.tgz", + "integrity": "sha512-zbBBOAOlzxfrU4WSnbCHk/nR6Vf32lSEPxDEvNOR08Z5DSZ/A6qJu0rqrHVcexBTd1hc2gim998xnqF/R1PuEw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", + "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", + "license": "MIT", + "dependencies": { + "tslib": "2.4.0" + } + }, + "node_modules/framesync/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "license": "0BSD" + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fuse.js": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz", + "integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-port-please": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz", + "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==", + "license": "MIT" + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/git-up": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-8.1.1.tgz", + "integrity": "sha512-FDenSF3fVqBYSaJoYy1KSc2wosx0gCvKP+c+PRBht7cAaiCeQlBtfBDX9vgnNOHmdePlSFITVcn4pFfcgNvx3g==", + "license": "MIT", + "dependencies": { + "is-ssh": "^1.4.0", + "parse-url": "^9.2.0" + } + }, + "node_modules/git-url-parse": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-16.1.0.tgz", + "integrity": "sha512-cPLz4HuK86wClEW7iDdeAKcCVlWXmrLpb2L+G9goW0Z1dtpNS6BXXSOckUTlJT/LDQViE1QZKstNORzHsLnobw==", + "license": "MIT", + "dependencies": { + "git-up": "^8.1.0" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-fonts-helper": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/google-fonts-helper/-/google-fonts-helper-3.7.3.tgz", + "integrity": "sha512-dENZeK6RMvkCOFoKbLbS+pyoDgpmk4U6BYmfXnztMar2d8SjEG6valxql/Lkyi7bu5F+3FTdE+gAEYSYZ5zzMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "deepmerge": "^4.3.1", + "hookable": "^5.5.3", + "ofetch": "^1.4.1", + "ufo": "^1.5.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/gzip-size": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-7.0.0.tgz", + "integrity": "sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==", + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/h3": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz", + "integrity": "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.2", + "crossws": "^0.3.5", + "defu": "^6.1.4", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.2", + "radix3": "^1.1.2", + "ufo": "^1.6.1", + "uncrypto": "^0.1.3" + } + }, + "node_modules/h3-compression": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/h3-compression/-/h3-compression-0.3.2.tgz", + "integrity": "sha512-B+yCKyDRnO0BXSfjAP4tCXJgJwmnKp3GyH5Yh66mY9KuOCrrGQSPk/gBFG2TgH7OyB/6mvqNZ1X0XNVuy0qRsw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/codedredd" + }, + "peerDependencies": { + "h3": "^1.6.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hex-rgb": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz", + "integrity": "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hey-listen": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", + "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==", + "license": "MIT" + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-assert/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-shutdown": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/http-shutdown/-/http-shutdown-1.2.2.tgz", + "integrity": "sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw==", + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/httpxy": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/httpxy/-/httpxy-0.1.7.tgz", + "integrity": "sha512-pXNx8gnANKAndgga5ahefxc++tJvNL87CXoRwxn1cJE2ZkWEojF3tNfQIEhZX/vfpt+wzeAzpUI4qkediX1MLQ==", + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-meta": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/image-meta/-/image-meta-0.2.1.tgz", + "integrity": "sha512-K6acvFaelNxx8wc2VjbIzXKDVB0Khs0QT35U6NkGfTdCmjLNcO2945m7RFNR9/RPVFm48hq7QPzK8uGH18HCGw==", + "license": "MIT" + }, + "node_modules/image-size": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", + "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", + "dev": true, + "license": "MIT", + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/impound": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/impound/-/impound-1.0.0.tgz", + "integrity": "sha512-8lAJ+1Arw2sMaZ9HE2ZmL5zOcMnt18s6+7Xqgq2aUVy4P1nlzAyPtzCDxsk51KVFwHEEdc6OWvUyqwHwhRYaug==", + "license": "MIT", + "dependencies": { + "exsolve": "^1.0.5", + "mocked-exports": "^0.1.1", + "pathe": "^2.0.3", + "unplugin": "^2.3.2", + "unplugin-utils": "^0.2.4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ioredis": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.7.0.tgz", + "integrity": "sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==", + "license": "MIT", + "dependencies": { + "@ioredis/commands": "^1.3.0", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/ipx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ipx/-/ipx-2.1.1.tgz", + "integrity": "sha512-XuM9FEGOT+/45mfAWZ5ykwkZ/oE7vWpd1iWjRffMWlwAYIRzb/xD6wZhQ4BzmPMX6Ov5dqK0wUyD0OEN9oWT6g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@fastify/accept-negotiator": "^1.1.0", + "citty": "^0.1.5", + "consola": "^3.2.3", + "defu": "^6.1.4", + "destr": "^2.0.2", + "etag": "^1.8.1", + "h3": "^1.10.0", + "image-meta": "^0.2.0", + "listhen": "^1.5.6", + "ofetch": "^1.3.3", + "pathe": "^1.1.2", + "sharp": "^0.32.6", + "svgo": "^3.2.0", + "ufo": "^1.3.2", + "unstorage": "^1.10.1", + "xss": "^1.0.14" + }, + "bin": { + "ipx": "bin/ipx.mjs" + } + }, + "node_modules/ipx/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-installed-globally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.0.tgz", + "integrity": "sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==", + "license": "MIT", + "dependencies": { + "global-directory": "^4.0.1", + "is-path-inside": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-ssh": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.1.tgz", + "integrity": "sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==", + "license": "MIT", + "dependencies": { + "protocols": "^2.0.1" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is64bit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is64bit/-/is64bit-2.0.0.tgz", + "integrity": "sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==", + "license": "MIT", + "dependencies": { + "system-architecture": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/knitwork": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/knitwork/-/knitwork-1.2.0.tgz", + "integrity": "sha512-xYSH7AvuQ6nXkq42x0v5S8/Iry+cfulBz/DJQzhIyESdLD7425jXsPy4vn5cCXU+HhRN2kVw51Vd1K6/By4BQg==", + "license": "MIT" + }, + "node_modules/koa": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.2.tgz", + "integrity": "sha512-+CCssgnrWKx9aI3OeZwroa/ckG4JICxvIFnSiOUyl2Uv+UTI+xIw0FfFrWS7cQFpoePpr9o8csss7KzsTzNL8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.9.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "engines": { + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/koa-convert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", + "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", + "dev": true, + "license": "MIT", + "dependencies": { + "co": "^4.6.0", + "koa-compose": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/koa-send": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz", + "integrity": "sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "http-errors": "^1.7.3", + "resolve-path": "^1.4.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/koa-send/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-send/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-send/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-static": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", + "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.1.0", + "koa-send": "^5.0.0" + }, + "engines": { + "node": ">= 7.6.0" + } + }, + "node_modules/koa-static/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/koa/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/koa/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/launch-editor": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz", + "integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lighthouse-logger": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.2.tgz", + "integrity": "sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.1", + "marky": "^1.2.2" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/listhen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/listhen/-/listhen-1.9.0.tgz", + "integrity": "sha512-I8oW2+QL5KJo8zXNWX046M134WchxsXC7SawLPvRQpogCbkyQIaFxPE89A2HiwR7vAK2Dm2ERBAmyjTYGYEpBg==", + "license": "MIT", + "dependencies": { + "@parcel/watcher": "^2.4.1", + "@parcel/watcher-wasm": "^2.4.1", + "citty": "^0.1.6", + "clipboardy": "^4.0.0", + "consola": "^3.2.3", + "crossws": ">=0.2.0 <0.4.0", + "defu": "^6.1.4", + "get-port-please": "^3.1.2", + "h3": "^1.12.0", + "http-shutdown": "^1.2.2", + "jiti": "^2.1.2", + "mlly": "^1.7.1", + "node-forge": "^1.3.1", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "ufo": "^1.5.4", + "untun": "^0.1.3", + "uqr": "^0.1.2" + }, + "bin": { + "listen": "bin/listhen.mjs", + "listhen": "bin/listhen.mjs" + } + }, + "node_modules/listhen/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-vue-next": { + "version": "0.544.0", + "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.544.0.tgz", + "integrity": "sha512-mDp/AdGOPIDkpFHnFiTWgQgCST9aBXHVaiobZfOMIvv7nrOukzF/TP+7KoOwrngdWRaH9TMiepMBIX1vsgKJ3g==", + "license": "ISC", + "peerDependencies": { + "vue": ">=3.0.1" + } + }, + "node_modules/magic-regexp": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/magic-regexp/-/magic-regexp-0.10.0.tgz", + "integrity": "sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg==", + "license": "MIT", + "dependencies": { + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12", + "mlly": "^1.7.2", + "regexp-tree": "^0.1.27", + "type-level-regexp": "~0.1.17", + "ufo": "^1.5.4", + "unplugin": "^2.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magic-string-ast": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-0.7.1.tgz", + "integrity": "sha512-ub9iytsEbT7Yw/Pd29mSo/cNQpaEu67zR1VVcXDiYjSFwzeBxNdTd0FMnSslLQXiRj8uGPzwsaoefrMD5XAmdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17" + }, + "engines": { + "node": ">=16.14.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/marky": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", + "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true, + "license": "CC0-1.0", + "optional": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/mocked-exports": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/mocked-exports/-/mocked-exports-0.1.1.tgz", + "integrity": "sha512-aF7yRQr/Q0O2/4pIXm6PZ5G+jAd7QS4Yu8m+WEeEHGnbo+7mE36CbLSDQiXYV8bVL3NfmdeqPJct0tUlnjVSnA==", + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/nanotar": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nanotar/-/nanotar-0.2.0.tgz", + "integrity": "sha512-9ca1h0Xjvo9bEkE4UOxgAzLV0jHKe6LMaxo37ND2DAhhAtd0j8pR1Wxz+/goMrZO8AEZTWCmyaOsFI/W5AdpCQ==", + "license": "MIT" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nitropack": { + "version": "2.12.6", + "resolved": "https://registry.npmjs.org/nitropack/-/nitropack-2.12.6.tgz", + "integrity": "sha512-DEq31s0SP4/Z5DIoVBRo9DbWFPWwIoYD4cQMEz7eE+iJMiAP+1k9A3B9kcc6Ihc0jDJmfUcHYyh6h2XlynCx6g==", + "license": "MIT", + "dependencies": { + "@cloudflare/kv-asset-handler": "^0.4.0", + "@rollup/plugin-alias": "^5.1.1", + "@rollup/plugin-commonjs": "^28.0.6", + "@rollup/plugin-inject": "^5.0.5", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.1", + "@rollup/plugin-replace": "^6.0.2", + "@rollup/plugin-terser": "^0.4.4", + "@vercel/nft": "^0.30.1", + "archiver": "^7.0.1", + "c12": "^3.2.0", + "chokidar": "^4.0.3", + "citty": "^0.1.6", + "compatx": "^0.2.0", + "confbox": "^0.2.2", + "consola": "^3.4.2", + "cookie-es": "^2.0.0", + "croner": "^9.1.0", + "crossws": "^0.3.5", + "db0": "^0.3.2", + "defu": "^6.1.4", + "destr": "^2.0.5", + "dot-prop": "^9.0.0", + "esbuild": "^0.25.9", + "escape-string-regexp": "^5.0.0", + "etag": "^1.8.1", + "exsolve": "^1.0.7", + "globby": "^14.1.0", + "gzip-size": "^7.0.0", + "h3": "^1.15.4", + "hookable": "^5.5.3", + "httpxy": "^0.1.7", + "ioredis": "^5.7.0", + "jiti": "^2.5.1", + "klona": "^2.0.6", + "knitwork": "^1.2.0", + "listhen": "^1.9.0", + "magic-string": "^0.30.19", + "magicast": "^0.3.5", + "mime": "^4.0.7", + "mlly": "^1.8.0", + "node-fetch-native": "^1.6.7", + "node-mock-http": "^1.0.3", + "ofetch": "^1.4.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.0.0", + "pkg-types": "^2.3.0", + "pretty-bytes": "^7.0.1", + "radix3": "^1.1.2", + "rollup": "^4.50.1", + "rollup-plugin-visualizer": "^6.0.3", + "scule": "^1.3.0", + "semver": "^7.7.2", + "serve-placeholder": "^2.0.2", + "serve-static": "^2.2.0", + "source-map": "^0.7.6", + "std-env": "^3.9.0", + "ufo": "^1.6.1", + "ultrahtml": "^1.6.0", + "uncrypto": "^0.1.3", + "unctx": "^2.4.1", + "unenv": "^2.0.0-rc.21", + "unimport": "^5.2.0", + "unplugin-utils": "^0.3.0", + "unstorage": "^1.17.1", + "untyped": "^2.0.0", + "unwasm": "^0.3.11", + "youch": "^4.1.0-beta.11", + "youch-core": "^0.3.3" + }, + "bin": { + "nitro": "dist/cli/index.mjs", + "nitropack": "dist/cli/index.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "xml2js": "^0.6.2" + }, + "peerDependenciesMeta": { + "xml2js": { + "optional": true + } + } + }, + "node_modules/nitropack/node_modules/cookie-es": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-2.0.0.tgz", + "integrity": "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==", + "license": "MIT" + }, + "node_modules/nitropack/node_modules/unplugin-utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.0.tgz", + "integrity": "sha512-JLoggz+PvLVMJo+jZt97hdIIIZ2yTzGgft9e9q8iMrC4ewufl62ekeW7mixBghonn2gVb/ICjyvlmOCUBnJLQg==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/node-abi": { + "version": "3.77.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.77.0.tgz", + "integrity": "sha512-DSmt0OEcLoK4i3NuscSbGjOf3bqiDEutejqENSplMSFA/gmB8mkED9G4pKWnPl7MDU4rSHebKPHeitpDfyH0cQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-mock-http": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.3.tgz", + "integrity": "sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "license": "MIT" + }, + "node_modules/nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nuxt": { + "version": "3.19.2", + "resolved": "https://registry.npmjs.org/nuxt/-/nuxt-3.19.2.tgz", + "integrity": "sha512-z4ouGRMOWqZ1xaZ+HdRBRVlZcKSoDBpRxQ30GJ2dllraZMC/gNpTGuY32H3xP5b4R29b8uYcK+B8LFQoRHpO8A==", + "license": "MIT", + "dependencies": { + "@nuxt/cli": "^3.28.0", + "@nuxt/devalue": "^2.0.2", + "@nuxt/devtools": "^2.6.3", + "@nuxt/kit": "3.19.2", + "@nuxt/schema": "3.19.2", + "@nuxt/telemetry": "^2.6.6", + "@nuxt/vite-builder": "3.19.2", + "@unhead/vue": "^2.0.14", + "@vue/shared": "^3.5.21", + "c12": "^3.2.0", + "chokidar": "^4.0.3", + "compatx": "^0.2.0", + "consola": "^3.4.2", + "cookie-es": "^2.0.0", + "defu": "^6.1.4", + "destr": "^2.0.5", + "devalue": "^5.3.2", + "errx": "^0.1.0", + "esbuild": "^0.25.9", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "exsolve": "^1.0.7", + "h3": "^1.15.4", + "hookable": "^5.5.3", + "ignore": "^7.0.5", + "impound": "^1.0.0", + "jiti": "^2.5.1", + "klona": "^2.0.6", + "knitwork": "^1.2.0", + "magic-string": "^0.30.19", + "mlly": "^1.8.0", + "mocked-exports": "^0.1.1", + "nanotar": "^0.2.0", + "nitropack": "^2.12.5", + "nypm": "^0.6.1", + "ofetch": "^1.4.1", + "ohash": "^2.0.11", + "on-change": "^5.0.1", + "oxc-minify": "^0.87.0", + "oxc-parser": "^0.87.0", + "oxc-transform": "^0.87.0", + "oxc-walker": "^0.5.2", + "pathe": "^2.0.3", + "perfect-debounce": "^2.0.0", + "pkg-types": "^2.3.0", + "radix3": "^1.1.2", + "scule": "^1.3.0", + "semver": "^7.7.2", + "std-env": "^3.9.0", + "tinyglobby": "^0.2.15", + "ufo": "^1.6.1", + "ultrahtml": "^1.6.0", + "uncrypto": "^0.1.3", + "unctx": "^2.4.1", + "unimport": "^5.2.0", + "unplugin": "^2.3.10", + "unplugin-vue-router": "^0.15.0", + "unstorage": "^1.17.1", + "untyped": "^2.0.0", + "vue": "^3.5.21", + "vue-bundle-renderer": "^2.1.2", + "vue-devtools-stub": "^0.1.0", + "vue-router": "^4.5.1" + }, + "bin": { + "nuxi": "bin/nuxt.mjs", + "nuxt": "bin/nuxt.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@parcel/watcher": "^2.1.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "peerDependenciesMeta": { + "@parcel/watcher": { + "optional": true + }, + "@types/node": { + "optional": true + } + } + }, + "node_modules/nuxt-link-checker": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/nuxt-link-checker/-/nuxt-link-checker-4.3.2.tgz", + "integrity": "sha512-ehEeoe1OFW2r9ZC7DCneYsk2eupyi5udLpbqbTtJOiqoCddbDZ8/bv7k6kBwbQAAyZPJFptGhwas8n4hUUY3Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/devtools-kit": "^2.6.3", + "@nuxt/kit": "^4.1.2", + "@vueuse/core": "^13.9.0", + "consola": "^3.4.2", + "diff": "^8.0.2", + "fuse.js": "^7.1.0", + "magic-string": "^0.30.19", + "nuxt-site-config": "^3.2.5", + "ofetch": "^1.4.1", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "radix3": "^1.1.2", + "sirv": "^3.0.2", + "std-env": "^3.9.0", + "ufo": "^1.6.1", + "ultrahtml": "^1.6.0", + "unstorage": "^1.17.1" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/nuxt-link-checker/node_modules/@nuxt/kit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.1.2.tgz", + "integrity": "sha512-P5q41xeEOa6ZQC0PvIP7TSBmOAMxXK4qihDcCbYIJq8RcVsEPbGZVlidmxE6EOw1ucSyodq9nbV31FAKwoL4NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "c12": "^3.2.0", + "consola": "^3.4.2", + "defu": "^6.1.4", + "destr": "^2.0.5", + "errx": "^0.1.0", + "exsolve": "^1.0.7", + "ignore": "^7.0.5", + "jiti": "^2.5.1", + "klona": "^2.0.6", + "mlly": "^1.8.0", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "rc9": "^2.1.2", + "scule": "^1.3.0", + "semver": "^7.7.2", + "std-env": "^3.9.0", + "tinyglobby": "^0.2.15", + "ufo": "^1.6.1", + "unctx": "^2.4.1", + "unimport": "^5.2.0", + "untyped": "^2.0.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/nuxt-og-image": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/nuxt-og-image/-/nuxt-og-image-4.2.0.tgz", + "integrity": "sha512-cwvzefOsF5woIuNit72/8fP5ha4iGr6bTkSvtglH5RYB/WHbXrPSFOiMai015s8UJeW8LCguWo/+RyRFlG4Pig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/devtools-kit": "2.1.3", + "@nuxt/kit": "^3.15.4", + "@resvg/resvg-js": "^2.6.2", + "@resvg/resvg-wasm": "^2.6.2", + "@unhead/ssr": "^1.11.20", + "@unocss/core": "^66.0.0", + "@unocss/preset-wind3": "^66.0.0", + "chrome-launcher": "^1.1.2", + "consola": "^3.4.0", + "defu": "^6.1.4", + "execa": "^9.5.2", + "image-size": "^2.0.0", + "magic-string": "^0.30.17", + "nuxt-site-config": "^3.1.3", + "nypm": "^0.6.0", + "ofetch": "^1.4.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "pkg-types": "^2.1.0", + "playwright-core": "^1.50.1", + "radix3": "^1.1.2", + "satori": "^0.12.1", + "satori-html": "^0.3.2", + "sirv": "^3.0.1", + "std-env": "^3.8.1", + "strip-literal": "^3.0.0", + "ufo": "^1.5.4", + "unplugin": "^2.2.0", + "unwasm": "^0.3.9", + "yoga-wasm-web": "^0.3.3" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "@unhead/vue": "^1.0.0 || ^2.0.0", + "unstorage": "^1.0.0" + } + }, + "node_modules/nuxt-og-image/node_modules/@nuxt/devtools-kit": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nuxt/devtools-kit/-/devtools-kit-2.1.3.tgz", + "integrity": "sha512-OlLo8LZTp71Wi9q0ooE1bYeS5P7P1sU3JUb+zqJRX6/jMQa2QP06hc6TOz/5AIsEOq8YPQTum4oJ9gpHx53B0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.15.4", + "@nuxt/schema": "^3.15.4", + "execa": "^9.5.2" + }, + "peerDependencies": { + "vite": ">=6.0" + } + }, + "node_modules/nuxt-og-image/node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nuxt-og-image/node_modules/execa": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", + "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/nuxt-og-image/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nuxt-og-image/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/nuxt-og-image/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nuxt-og-image/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nuxt-og-image/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nuxt-og-image/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nuxt-schema-org": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/nuxt-schema-org/-/nuxt-schema-org-4.1.3.tgz", + "integrity": "sha512-xUhWCKlWjp6J5ktmmdz4eYPl2fifmqQtVXWharnKXLRATH5z2j4ayA1taZXUuoOZLlKAXIP1HUEm23ZCI+/DhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.15.4", + "@unhead/schema-org": "^1.11.19", + "defu": "^6.1.4", + "nuxt-site-config": "^3.1.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "sirv": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/nuxt-schema-org/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/nuxt-schema-org/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/nuxt-seo-utils": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/nuxt-seo-utils/-/nuxt-seo-utils-6.0.13.tgz", + "integrity": "sha512-3D4FEeJOqWhmF6B4VRxKOUkdzhNLzookHy/eZPXz82xT/drTkpz5/faPnL29Y9WBOhNv49e3xeUvUoJd+gpT+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.15.4", + "@unhead/addons": "^1.11.19", + "defu": "^6.1.4", + "escape-string-regexp": "^5.0.0", + "fast-glob": "^3.3.3", + "image-size": "^1.2.0", + "mlly": "^1.7.4", + "nuxt-site-config": "^3.1.0", + "pathe": "^2.0.3", + "scule": "^1.3.0", + "ufo": "^1.5.4" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/nuxt-seo-utils/node_modules/image-size": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "dev": true, + "license": "MIT", + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/nuxt-site-config": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/nuxt-site-config/-/nuxt-site-config-3.2.8.tgz", + "integrity": "sha512-mmxqqh1abQ6ObNdK0OsvbYr5i/35RYAN3638CAUxTG1cJkihhj5CSGAcdSb4XjjqW6PFsVTT9LW4M/g2jrOXmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^4.1.2", + "nuxt-site-config-kit": "3.2.8", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "sirv": "^3.0.2", + "site-config-stack": "3.2.8", + "ufo": "^1.6.1" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "h3": "^1" + } + }, + "node_modules/nuxt-site-config-kit": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/nuxt-site-config-kit/-/nuxt-site-config-kit-3.2.8.tgz", + "integrity": "sha512-g6h+PDgvZOmFbTZyvz4CozicUtepBIe7bwS+tklPfaCvbuy6XPXvl+dkMO54VamCOj+9d5190HMHe+ZpQEyZFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^4.1.2", + "pkg-types": "^2.3.0", + "site-config-stack": "3.2.8", + "std-env": "^3.9.0", + "ufo": "^1.6.1" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/nuxt-site-config-kit/node_modules/@nuxt/kit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.1.2.tgz", + "integrity": "sha512-P5q41xeEOa6ZQC0PvIP7TSBmOAMxXK4qihDcCbYIJq8RcVsEPbGZVlidmxE6EOw1ucSyodq9nbV31FAKwoL4NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "c12": "^3.2.0", + "consola": "^3.4.2", + "defu": "^6.1.4", + "destr": "^2.0.5", + "errx": "^0.1.0", + "exsolve": "^1.0.7", + "ignore": "^7.0.5", + "jiti": "^2.5.1", + "klona": "^2.0.6", + "mlly": "^1.8.0", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "rc9": "^2.1.2", + "scule": "^1.3.0", + "semver": "^7.7.2", + "std-env": "^3.9.0", + "tinyglobby": "^0.2.15", + "ufo": "^1.6.1", + "unctx": "^2.4.1", + "unimport": "^5.2.0", + "untyped": "^2.0.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/nuxt-site-config/node_modules/@nuxt/kit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.1.2.tgz", + "integrity": "sha512-P5q41xeEOa6ZQC0PvIP7TSBmOAMxXK4qihDcCbYIJq8RcVsEPbGZVlidmxE6EOw1ucSyodq9nbV31FAKwoL4NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "c12": "^3.2.0", + "consola": "^3.4.2", + "defu": "^6.1.4", + "destr": "^2.0.5", + "errx": "^0.1.0", + "exsolve": "^1.0.7", + "ignore": "^7.0.5", + "jiti": "^2.5.1", + "klona": "^2.0.6", + "mlly": "^1.8.0", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "rc9": "^2.1.2", + "scule": "^1.3.0", + "semver": "^7.7.2", + "std-env": "^3.9.0", + "tinyglobby": "^0.2.15", + "ufo": "^1.6.1", + "unctx": "^2.4.1", + "unimport": "^5.2.0", + "untyped": "^2.0.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/nuxt/node_modules/cookie-es": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-2.0.0.tgz", + "integrity": "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==", + "license": "MIT" + }, + "node_modules/nypm": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", + "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.2", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "tinyexec": "^1.0.1" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/ofetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.4.1.tgz", + "integrity": "sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.3", + "node-fetch-native": "^1.6.4", + "ufo": "^1.5.4" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, + "node_modules/on-change": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/on-change/-/on-change-5.0.1.tgz", + "integrity": "sha512-n7THCP7RkyReRSLkJb8kUWoNsxUIBxTkIp3JKno+sEz6o/9AJ3w3P9fzQkITEkMwyTKJjZciF3v/pVoouxZZMg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/on-change?sponsor=1" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==", + "dev": true + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/oxc-minify": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/oxc-minify/-/oxc-minify-0.87.0.tgz", + "integrity": "sha512-+UHWp6+0mdq0S2rEsZx9mqgL6JnG9ogO+CU17XccVrPUFtISFcZzk/biTn1JdBYFQ3kztof19pv8blMtgStQ2g==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-minify/binding-android-arm64": "0.87.0", + "@oxc-minify/binding-darwin-arm64": "0.87.0", + "@oxc-minify/binding-darwin-x64": "0.87.0", + "@oxc-minify/binding-freebsd-x64": "0.87.0", + "@oxc-minify/binding-linux-arm-gnueabihf": "0.87.0", + "@oxc-minify/binding-linux-arm-musleabihf": "0.87.0", + "@oxc-minify/binding-linux-arm64-gnu": "0.87.0", + "@oxc-minify/binding-linux-arm64-musl": "0.87.0", + "@oxc-minify/binding-linux-riscv64-gnu": "0.87.0", + "@oxc-minify/binding-linux-s390x-gnu": "0.87.0", + "@oxc-minify/binding-linux-x64-gnu": "0.87.0", + "@oxc-minify/binding-linux-x64-musl": "0.87.0", + "@oxc-minify/binding-wasm32-wasi": "0.87.0", + "@oxc-minify/binding-win32-arm64-msvc": "0.87.0", + "@oxc-minify/binding-win32-x64-msvc": "0.87.0" + } + }, + "node_modules/oxc-parser": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.87.0.tgz", + "integrity": "sha512-uc47XrtHwkBoES4HFgwgfH9sqwAtJXgAIBq4fFBMZ4hWmgVZoExyn+L4g4VuaecVKXkz1bvlaHcfwHAJPQb5Gw==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.87.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm64": "0.87.0", + "@oxc-parser/binding-darwin-arm64": "0.87.0", + "@oxc-parser/binding-darwin-x64": "0.87.0", + "@oxc-parser/binding-freebsd-x64": "0.87.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.87.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.87.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.87.0", + "@oxc-parser/binding-linux-arm64-musl": "0.87.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.87.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.87.0", + "@oxc-parser/binding-linux-x64-gnu": "0.87.0", + "@oxc-parser/binding-linux-x64-musl": "0.87.0", + "@oxc-parser/binding-wasm32-wasi": "0.87.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.87.0", + "@oxc-parser/binding-win32-x64-msvc": "0.87.0" + } + }, + "node_modules/oxc-transform": { + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/oxc-transform/-/oxc-transform-0.87.0.tgz", + "integrity": "sha512-dt6INKWY2DKbSc8yR9VQoqBsCjPQ3z/SKv882UqlwFve+K38xtpi2avDlvNd35SpHUwDLDFoV3hMX0U3qOSaaQ==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-transform/binding-android-arm64": "0.87.0", + "@oxc-transform/binding-darwin-arm64": "0.87.0", + "@oxc-transform/binding-darwin-x64": "0.87.0", + "@oxc-transform/binding-freebsd-x64": "0.87.0", + "@oxc-transform/binding-linux-arm-gnueabihf": "0.87.0", + "@oxc-transform/binding-linux-arm-musleabihf": "0.87.0", + "@oxc-transform/binding-linux-arm64-gnu": "0.87.0", + "@oxc-transform/binding-linux-arm64-musl": "0.87.0", + "@oxc-transform/binding-linux-riscv64-gnu": "0.87.0", + "@oxc-transform/binding-linux-s390x-gnu": "0.87.0", + "@oxc-transform/binding-linux-x64-gnu": "0.87.0", + "@oxc-transform/binding-linux-x64-musl": "0.87.0", + "@oxc-transform/binding-wasm32-wasi": "0.87.0", + "@oxc-transform/binding-win32-arm64-msvc": "0.87.0", + "@oxc-transform/binding-win32-x64-msvc": "0.87.0" + } + }, + "node_modules/oxc-walker": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/oxc-walker/-/oxc-walker-0.5.2.tgz", + "integrity": "sha512-XYoZqWwApSKUmSDEFeOKdy3Cdh95cOcSU8f7yskFWE4Rl3cfL5uwyY+EV7Brk9mdNLy+t5SseJajd6g7KncvlA==", + "license": "MIT", + "dependencies": { + "magic-regexp": "^0.10.0" + }, + "peerDependencies": { + "oxc-parser": ">=0.72.0" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/package-manager-detector": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.3.0.tgz", + "integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==", + "license": "MIT" + }, + "node_modules/packrup": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/packrup/-/packrup-0.1.2.tgz", + "integrity": "sha512-ZcKU7zrr5GlonoS9cxxrb5HVswGnyj6jQvwFBa6p5VFw7G71VAHcUKL5wyZSU/ECtPM/9gacWxy2KFQKt1gMNA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-css-color": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/parse-css-color/-/parse-css-color-0.2.1.tgz", + "integrity": "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "^1.1.4", + "hex-rgb": "^4.1.0" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-path": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.1.0.tgz", + "integrity": "sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==", + "license": "MIT", + "dependencies": { + "protocols": "^2.0.0" + } + }, + "node_modules/parse-url": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-9.2.0.tgz", + "integrity": "sha512-bCgsFI+GeGWPAvAiUv63ZorMeif3/U0zaXABGJbOWt5OH2KCaPHF6S+0ok4aqM9RuIPGyZdx9tR9l13PsW4AYQ==", + "license": "MIT", + "dependencies": { + "@types/parse-path": "^7.0.0", + "parse-path": "^7.0.0" + }, + "engines": { + "node": ">=14.13.0" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz", + "integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/playwright-core": { + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz", + "integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/popmotion": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.5.tgz", + "integrity": "sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==", + "license": "MIT", + "dependencies": { + "framesync": "6.1.2", + "hey-listen": "^1.0.8", + "style-value-types": "5.1.2", + "tslib": "2.4.0" + } + }, + "node_modules/popmotion/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "license": "0BSD" + }, + "node_modules/portfinder": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz", + "integrity": "sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.6", + "debug": "^4.3.6" + }, + "engines": { + "node": ">= 10.12" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-calc": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", + "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12 || ^20.9 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.38" + } + }, + "node_modules/postcss-calc/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-colormin": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.4.tgz", + "integrity": "sha512-ziQuVzQZBROpKpfeDwmrG+Vvlr0YWmY/ZAk99XD+mGEBuEojoFekL41NCsdhyNUtZI7DPOoIWIR7vQQK9xwluw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.1", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-convert-values": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.7.tgz", + "integrity": "sha512-HR9DZLN04Xbe6xugRH6lS4ZQH2zm/bFh/ZyRkpedZozhvh+awAfbA0P36InO4fZfDhvYfNJeNvlTf1sjwGbw/A==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-discard-comments": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.4.tgz", + "integrity": "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.1.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-discard-comments/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.2.tgz", + "integrity": "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==", + "license": "MIT", + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-discard-empty": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.1.tgz", + "integrity": "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==", + "license": "MIT", + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.1.tgz", + "integrity": "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==", + "license": "MIT", + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-merge-longhand": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz", + "integrity": "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^7.0.5" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-merge-rules": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.6.tgz", + "integrity": "sha512-2jIPT4Tzs8K87tvgCpSukRQ2jjd+hH6Bb8rEEOUDmmhOeTcqDg5fEFK8uKIu+Pvc3//sm3Uu6FRqfyv7YF7+BQ==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.1", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^5.0.1", + "postcss-selector-parser": "^7.1.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-merge-rules/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.1.tgz", + "integrity": "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.1.tgz", + "integrity": "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==", + "license": "MIT", + "dependencies": { + "colord": "^2.9.3", + "cssnano-utils": "^5.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-minify-params": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.4.tgz", + "integrity": "sha512-3OqqUddfH8c2e7M35W6zIwv7jssM/3miF9cbCSb1iJiWvtguQjlxZGIHK9JRmc8XAKmE2PFGtHSM7g/VcW97sw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.1", + "cssnano-utils": "^5.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.5.tgz", + "integrity": "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "postcss-selector-parser": "^7.1.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-minify-selectors/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-nesting": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", + "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-resolve-nested": "^3.1.0", + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", + "integrity": "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.1.tgz", + "integrity": "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==", + "license": "MIT", + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.1.tgz", + "integrity": "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.1.tgz", + "integrity": "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.1.tgz", + "integrity": "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-normalize-string": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.1.tgz", + "integrity": "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.1.tgz", + "integrity": "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.4.tgz", + "integrity": "sha512-LvIURTi1sQoZqj8mEIE8R15yvM+OhbR1avynMtI9bUzj5gGKR/gfZFd8O7VMj0QgJaIFzxDwxGl/ASMYAkqO8g==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-normalize-url": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.1.tgz", + "integrity": "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.1.tgz", + "integrity": "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-ordered-values": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.2.tgz", + "integrity": "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==", + "license": "MIT", + "dependencies": { + "cssnano-utils": "^5.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.4.tgz", + "integrity": "sha512-rdIC9IlMBn7zJo6puim58Xd++0HdbvHeHaPgXsimMfG1ijC5A9ULvNLSE0rUKVJOvNMcwewW4Ga21ngyJjY/+Q==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.1", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.1.tgz", + "integrity": "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-svgo": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.0.tgz", + "integrity": "sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^4.0.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >= 18" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-svgo/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/postcss-svgo/node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/postcss-svgo/node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" + }, + "node_modules/postcss-svgo/node_modules/svgo": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.4.1" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz", + "integrity": "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.1.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-unique-selectors/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/prebuild-install/node_modules/detect-libc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz", + "integrity": "sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/prebuild-install/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pretty-bytes": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-7.0.1.tgz", + "integrity": "sha512-285/jRCYIbMGDciDdrw0KPNC4LKEEwz/bwErcYNxSJOi4CpGUuLpb9gQpg3XJP0XYj9ldSRluXxih4lX2YN8Xw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/protocols": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.2.tgz", + "integrity": "sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "~2.0.3" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "license": "MIT", + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/replace-in-file": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-6.3.5.tgz", + "integrity": "sha512-arB9d3ENdKva2fxRnSjwBEXfK1npgyci7ZZuwysgAp7ORjHSyxz6oqIjTEv8R0Ydl4Ll7uOAZXL4vbkhGIizCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "glob": "^7.2.0", + "yargs": "^17.2.1" + }, + "bin": { + "replace-in-file": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/replace-in-file/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/replace-in-file/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/replace-in-file/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/replace-in-file/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/replace-in-file/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/replace-in-file/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/resolve-path/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/resolve-path/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/resolve-path/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" + }, + "node_modules/resolve-path/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/resolve-path/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.2.tgz", + "integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.50.2", + "@rollup/rollup-android-arm64": "4.50.2", + "@rollup/rollup-darwin-arm64": "4.50.2", + "@rollup/rollup-darwin-x64": "4.50.2", + "@rollup/rollup-freebsd-arm64": "4.50.2", + "@rollup/rollup-freebsd-x64": "4.50.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.50.2", + "@rollup/rollup-linux-arm-musleabihf": "4.50.2", + "@rollup/rollup-linux-arm64-gnu": "4.50.2", + "@rollup/rollup-linux-arm64-musl": "4.50.2", + "@rollup/rollup-linux-loong64-gnu": "4.50.2", + "@rollup/rollup-linux-ppc64-gnu": "4.50.2", + "@rollup/rollup-linux-riscv64-gnu": "4.50.2", + "@rollup/rollup-linux-riscv64-musl": "4.50.2", + "@rollup/rollup-linux-s390x-gnu": "4.50.2", + "@rollup/rollup-linux-x64-gnu": "4.50.2", + "@rollup/rollup-linux-x64-musl": "4.50.2", + "@rollup/rollup-openharmony-arm64": "4.50.2", + "@rollup/rollup-win32-arm64-msvc": "4.50.2", + "@rollup/rollup-win32-ia32-msvc": "4.50.2", + "@rollup/rollup-win32-x64-msvc": "4.50.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-visualizer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.3.tgz", + "integrity": "sha512-ZU41GwrkDcCpVoffviuM9Clwjy5fcUxlz0oMoTXTYsK+tcIFzbdacnrr2n8TXcHxbGKKXtOdjxM2HUS4HjkwIw==", + "license": "MIT", + "dependencies": { + "open": "^8.0.0", + "picomatch": "^4.0.2", + "source-map": "^0.7.4", + "yargs": "^17.5.1" + }, + "bin": { + "rollup-plugin-visualizer": "dist/bin/cli.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "rolldown": "1.x || ^1.0.0-beta", + "rollup": "2.x || 3.x || 4.x" + }, + "peerDependenciesMeta": { + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/satori": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/satori/-/satori-0.12.2.tgz", + "integrity": "sha512-3C/laIeE6UUe9A+iQ0A48ywPVCCMKCNSTU5Os101Vhgsjd3AAxGNjyq0uAA8kulMPK5n0csn8JlxPN9riXEjLA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "@shuding/opentype.js": "1.4.0-beta.0", + "css-background-parser": "^0.1.0", + "css-box-shadow": "1.0.0-3", + "css-gradient-parser": "^0.0.16", + "css-to-react-native": "^3.0.0", + "emoji-regex": "^10.2.1", + "escape-html": "^1.0.3", + "linebreak": "^1.1.0", + "parse-css-color": "^0.2.1", + "postcss-value-parser": "^4.2.0", + "yoga-wasm-web": "^0.3.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/satori-html": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/satori-html/-/satori-html-0.3.2.tgz", + "integrity": "sha512-wjTh14iqADFKDK80e51/98MplTGfxz2RmIzh0GqShlf4a67+BooLywF17TvJPD6phO0Hxm7Mf1N5LtRYvdkYRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ultrahtml": "^1.2.0" + } + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-placeholder": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/serve-placeholder/-/serve-placeholder-2.0.2.tgz", + "integrity": "sha512-/TMG8SboeiQbZJWRlfTCqMs2DD3SZgWp0kDQePz9yUuCnDfDh/92gf7/PxGhzXTKBIPASIHxFcZndoNbp6QOLQ==", + "license": "MIT", + "dependencies": { + "defu": "^6.1.4" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sharp": { + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", + "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.2", + "node-addon-api": "^6.1.0", + "prebuild-install": "^7.1.1", + "semver": "^7.5.4", + "simple-get": "^4.0.1", + "tar-fs": "^3.0.4", + "tunnel-agent": "^0.6.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/sharp/node_modules/detect-libc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz", + "integrity": "sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sharp/node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-git": { + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.28.0.tgz", + "integrity": "sha512-Rs/vQRwsn1ILH1oBUy8NucJlXmnnLeLCfcvbSehkPzbv3wwoFWIdtfd6Ndo6ZPhlPsCZ60CPI4rxurnwAa+a2w==", + "license": "MIT", + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/site-config-stack": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/site-config-stack/-/site-config-stack-3.2.8.tgz", + "integrity": "sha512-MZ55OBRmsuPT0P1D9HkTduchRsSGlWz+WiCDKmELO4FTxB+nuscHLrPAbS8bL2aEFlj/vDH8+bdTDezI4k7kXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ufo": "^1.6.1" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "vue": "^3" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/smob": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "license": "MIT" + }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "license": "MIT" + }, + "node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/structured-clone-es": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/structured-clone-es/-/structured-clone-es-1.0.0.tgz", + "integrity": "sha512-FL8EeKFFyNQv5cMnXI31CIMCsFarSVI2bF0U0ImeNE3g/F1IvJQyqzOXxPBRXiwQfyBTlbNe88jh1jFW0O/jiQ==", + "license": "ISC" + }, + "node_modules/style-value-types": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.1.2.tgz", + "integrity": "sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q==", + "license": "MIT", + "dependencies": { + "hey-listen": "^1.0.8", + "tslib": "2.4.0" + } + }, + "node_modules/style-value-types/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "license": "0BSD" + }, + "node_modules/stylehacks": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.6.tgz", + "integrity": "sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.1", + "postcss-selector-parser": "^7.1.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/stylehacks/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/superjson": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svgo": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/system-architecture": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-0.1.0.tgz", + "integrity": "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tailwind-config-viewer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/tailwind-config-viewer/-/tailwind-config-viewer-2.0.4.tgz", + "integrity": "sha512-icvcmdMmt9dphvas8wL40qttrHwAnW3QEN4ExJ2zICjwRsPj7gowd1cOceaWG3IfTuM/cTNGQcx+bsjMtmV+cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@koa/router": "^12.0.1", + "commander": "^6.0.0", + "fs-extra": "^9.0.1", + "koa": "^2.14.2", + "koa-static": "^5.0.0", + "open": "^7.0.4", + "portfinder": "^1.0.26", + "replace-in-file": "^6.1.0" + }, + "bin": { + "tailwind-config-viewer": "cli/index.js", + "tailwindcss-config-viewer": "cli/index.js" + }, + "engines": { + "node": ">=13" + }, + "peerDependencies": { + "tailwindcss": "1 || 2 || 2.0.1-compat || 3" + } + }, + "node_modules/tailwind-config-viewer/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/tailwind-config-viewer/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tailwind-config-viewer/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwind-config-viewer/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tailwindcss/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tailwindcss/node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/tailwindcss/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tailwindcss/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/tapable": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", + "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/terser": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", + "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "devOptional": true, + "license": "0BSD" + }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.x" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-level-regexp": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/type-level-regexp/-/type-level-regexp-0.1.17.tgz", + "integrity": "sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "license": "MIT" + }, + "node_modules/ultrahtml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", + "integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==", + "license": "MIT" + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/unctx": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/unctx/-/unctx-2.4.1.tgz", + "integrity": "sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg==", + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17", + "unplugin": "^2.1.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unenv": { + "version": "2.0.0-rc.21", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.21.tgz", + "integrity": "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A==", + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "exsolve": "^1.0.7", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "ufo": "^1.6.1" + } + }, + "node_modules/unhead": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/unhead/-/unhead-2.0.17.tgz", + "integrity": "sha512-xX3PCtxaE80khRZobyWCVxeFF88/Tg9eJDcJWY9us727nsTC7C449B8BUfVBmiF2+3LjPcmqeoB2iuMs0U4oJQ==", + "license": "MIT", + "dependencies": { + "hookable": "^5.5.3" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unimport": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/unimport/-/unimport-5.3.0.tgz", + "integrity": "sha512-cty7t1DESgm0OPfCy9oyn5u9B5t0tMW6tH6bXTjAGIO3SkJsbg/DXYHjrPrUKqultqbAAoltAfYsuu/FEDocjg==", + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "local-pkg": "^1.1.2", + "magic-string": "^0.30.19", + "mlly": "^1.8.0", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "pkg-types": "^2.3.0", + "scule": "^1.3.0", + "strip-literal": "^3.0.0", + "tinyglobby": "^0.2.15", + "unplugin": "^2.3.10", + "unplugin-utils": "^0.3.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unimport/node_modules/unplugin-utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.0.tgz", + "integrity": "sha512-JLoggz+PvLVMJo+jZt97hdIIIZ2yTzGgft9e9q8iMrC4ewufl62ekeW7mixBghonn2gVb/ICjyvlmOCUBnJLQg==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unplugin": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz", + "integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-ast": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/unplugin-ast/-/unplugin-ast-0.13.2.tgz", + "integrity": "sha512-WXr7/8RKzeT+VFWrnHUoodWtd/w6xGWJzxXGEDI011ngxx2hjpaCQZqeVWiE/af+iCoAnOTvZH/fy42YynxUYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^8.1.0", + "@babel/generator": "^7.26.5", + "ast-kit": "^1.4.0", + "magic-string-ast": "^0.7.0", + "unplugin": "^2.1.2", + "unplugin-utils": "^0.2.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-utils": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.2.5.tgz", + "integrity": "sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/unplugin-vue-router": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/unplugin-vue-router/-/unplugin-vue-router-0.15.0.tgz", + "integrity": "sha512-PyGehCjd9Ny9h+Uer4McbBjjib3lHihcyUEILa7pHKl6+rh8N7sFyw4ZkV+N30Oq2zmIUG7iKs3qpL0r+gXAaQ==", + "license": "MIT", + "dependencies": { + "@vue-macros/common": "3.0.0-beta.16", + "@vue/language-core": "^3.0.1", + "ast-walker-scope": "^0.8.1", + "chokidar": "^4.0.3", + "json5": "^2.2.3", + "local-pkg": "^1.1.1", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "muggle-string": "^0.4.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "scule": "^1.3.0", + "tinyglobby": "^0.2.14", + "unplugin": "^2.3.5", + "unplugin-utils": "^0.2.4", + "yaml": "^2.8.0" + }, + "peerDependencies": { + "@vue/compiler-sfc": "^3.5.17", + "vue-router": "^4.5.1" + }, + "peerDependenciesMeta": { + "vue-router": { + "optional": true + } + } + }, + "node_modules/unstorage": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.1.tgz", + "integrity": "sha512-KKGwRTT0iVBCErKemkJCLs7JdxNVfqTPc/85ae1XES0+bsHbc/sFBfVi5kJp156cc51BHinIH2l3k0EZ24vOBQ==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^4.0.3", + "destr": "^2.0.5", + "h3": "^1.15.4", + "lru-cache": "^10.4.3", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.4.1", + "ufo": "^1.6.1" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6.0.3 || ^7.0.0", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1.0.1", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/unstorage/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/untun": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/untun/-/untun-0.1.3.tgz", + "integrity": "sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ==", + "license": "MIT", + "dependencies": { + "citty": "^0.1.5", + "consola": "^3.2.3", + "pathe": "^1.1.1" + }, + "bin": { + "untun": "bin/untun.mjs" + } + }, + "node_modules/untun/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "license": "MIT" + }, + "node_modules/untyped": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/untyped/-/untyped-2.0.0.tgz", + "integrity": "sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==", + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "defu": "^6.1.4", + "jiti": "^2.4.2", + "knitwork": "^1.2.0", + "scule": "^1.3.0" + }, + "bin": { + "untyped": "dist/cli.mjs" + } + }, + "node_modules/unwasm": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/unwasm/-/unwasm-0.3.11.tgz", + "integrity": "sha512-Vhp5gb1tusSQw5of/g3Q697srYgMXvwMgXMjcG4ZNga02fDX9coxJ9fAb0Ci38hM2Hv/U1FXRPGgjP2BYqhNoQ==", + "license": "MIT", + "dependencies": { + "knitwork": "^1.2.0", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "pathe": "^2.0.3", + "pkg-types": "^2.2.0", + "unplugin": "^2.3.6" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uqr": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/uqr/-/uqr-0.1.2.tgz", + "integrity": "sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==", + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.6.tgz", + "integrity": "sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-dev-rpc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vite-dev-rpc/-/vite-dev-rpc-1.1.0.tgz", + "integrity": "sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==", + "license": "MIT", + "dependencies": { + "birpc": "^2.4.0", + "vite-hot-client": "^2.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0" + } + }, + "node_modules/vite-hot-client": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-2.1.0.tgz", + "integrity": "sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-plugin-checker": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.10.3.tgz", + "integrity": "sha512-f4sekUcDPF+T+GdbbE8idb1i2YplBAoH+SfRS0e/WRBWb2rYb1Jf5Pimll0Rj+3JgIYWwG2K5LtBPCXxoibkLg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "chokidar": "^4.0.3", + "npm-run-path": "^6.0.0", + "picocolors": "^1.1.1", + "picomatch": "^4.0.3", + "strip-ansi": "^7.1.0", + "tiny-invariant": "^1.3.3", + "tinyglobby": "^0.2.14", + "vscode-uri": "^3.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "peerDependencies": { + "@biomejs/biome": ">=1.7", + "eslint": ">=7", + "meow": "^13.2.0", + "optionator": "^0.9.4", + "stylelint": ">=16", + "typescript": "*", + "vite": ">=2.0.0", + "vls": "*", + "vti": "*", + "vue-tsc": "~2.2.10 || ^3.0.0" + }, + "peerDependenciesMeta": { + "@biomejs/biome": { + "optional": true + }, + "eslint": { + "optional": true + }, + "meow": { + "optional": true + }, + "optionator": { + "optional": true + }, + "stylelint": { + "optional": true + }, + "typescript": { + "optional": true + }, + "vls": { + "optional": true + }, + "vti": { + "optional": true + }, + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/vite-plugin-checker/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vite-plugin-checker/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vite-plugin-inspect": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-11.3.3.tgz", + "integrity": "sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==", + "license": "MIT", + "dependencies": { + "ansis": "^4.1.0", + "debug": "^4.4.1", + "error-stack-parser-es": "^1.0.5", + "ohash": "^2.0.11", + "open": "^10.2.0", + "perfect-debounce": "^2.0.0", + "sirv": "^3.0.1", + "unplugin-utils": "^0.3.0", + "vite-dev-rpc": "^1.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-inspect/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vite-plugin-inspect/node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vite-plugin-inspect/node_modules/unplugin-utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.0.tgz", + "integrity": "sha512-JLoggz+PvLVMJo+jZt97hdIIIZ2yTzGgft9e9q8iMrC4ewufl62ekeW7mixBghonn2gVb/ICjyvlmOCUBnJLQg==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/vite-plugin-vue-tracer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-tracer/-/vite-plugin-vue-tracer-1.0.0.tgz", + "integrity": "sha512-a+UB9IwGx5uwS4uG/a9kM6fCMnxONDkOTbgCUbhFpiGhqfxrrC1+9BibV7sWwUnwj1Dg6MnRxG0trLgUZslDXA==", + "license": "MIT", + "dependencies": { + "estree-walker": "^3.0.3", + "exsolve": "^1.0.7", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "source-map-js": "^1.2.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0", + "vue": "^3.5.0" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.21.tgz", + "integrity": "sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.21", + "@vue/compiler-sfc": "3.5.21", + "@vue/runtime-dom": "3.5.21", + "@vue/server-renderer": "3.5.21", + "@vue/shared": "3.5.21" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-bundle-renderer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vue-bundle-renderer/-/vue-bundle-renderer-2.1.2.tgz", + "integrity": "sha512-M4WRBO/O/7G9phGaGH9AOwOnYtY9ZpPoDVpBpRzR2jO5rFL9mgIlQIgums2ljCTC2HL1jDXFQc//CzWcAQHgAw==", + "license": "MIT", + "dependencies": { + "ufo": "^1.6.1" + } + }, + "node_modules/vue-devtools-stub": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/vue-devtools-stub/-/vue-devtools-stub-0.1.0.tgz", + "integrity": "sha512-RutnB7X8c5hjq39NceArgXg28WZtZpGc3+J16ljMiYnFhKvd8hITxSWQSQ5bvldxMDU6gG5mkxl1MTQLXckVSQ==", + "license": "MIT" + }, + "node_modules/vue-router": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz", + "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xss": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz", + "integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "commander": "^2.20.3", + "cssfilter": "0.0.10" + }, + "bin": { + "xss": "bin/xss" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/xss/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ylru": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz", + "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoga-wasm-web": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz", + "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==", + "dev": true, + "license": "MIT" + }, + "node_modules/youch": { + "version": "4.1.0-beta.11", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.11.tgz", + "integrity": "sha512-sQi6PERyO/mT8w564ojOVeAlYTtVQmC2GaktQAf+IdI75/GKIggosBuvyVXvEV+FATAT6RbLdIjFoiIId4ozoQ==", + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" + } + }, + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", + "license": "MIT", + "dependencies": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" + } + }, + "node_modules/zhead": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/zhead/-/zhead-2.2.4.tgz", + "integrity": "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + } + } +} diff --git a/apps/website/package.json b/apps/website/package.json new file mode 100644 index 0000000..8392d76 --- /dev/null +++ b/apps/website/package.json @@ -0,0 +1,33 @@ +{ + "name": "@harborsmith/website", + "version": "1.0.0", + "type": "module", + "private": true, + "scripts": { + "build": "nuxt build", + "dev": "nuxt dev", + "generate": "nuxt generate", + "preview": "nuxt preview", + "postinstall": "nuxt prepare", + "lint": "eslint .", + "typecheck": "nuxt typecheck" + }, + "dependencies": { + "@iconify-json/lucide": "^1.2.68", + "@vueuse/motion": "^2.2.6", + "@vueuse/nuxt": "^11.3.0", + "lucide-vue-next": "^0.544.0", + "nuxt": "^3.15.0", + "unenv": "^2.0.0-rc.21", + "vue": "latest", + "vue-router": "latest" + }, + "devDependencies": { + "@nuxt/image": "^1.8.1", + "@nuxtjs/google-fonts": "^3.2.0", + "@nuxtjs/seo": "^2.0.0", + "@nuxtjs/tailwindcss": "^6.12.2", + "@types/node": "^20", + "typescript": "^5.7.2" + } +} diff --git a/apps/website/pages/index.vue b/apps/website/pages/index.vue new file mode 100644 index 0000000..c40d5de --- /dev/null +++ b/apps/website/pages/index.vue @@ -0,0 +1,55 @@ + + + diff --git a/apps/website/plugins/lucide.client.js b/apps/website/plugins/lucide.client.js new file mode 100644 index 0000000..2645726 --- /dev/null +++ b/apps/website/plugins/lucide.client.js @@ -0,0 +1,47 @@ +import { + Star, + Phone, + Wrench, + ChevronDown, + Ship, + Award, + Users, + ShieldCheck, + Calendar, + CalendarCheck, + Mail, + MapPin, + Clock, + Facebook, + Instagram, + Twitter, + Check, + Menu, + X, + Truck +} from 'lucide-vue-next' + +export default defineNuxtPlugin((nuxtApp) => { + // Register Lucide icons as global components + nuxtApp.vueApp.component('LucideStar', Star) + nuxtApp.vueApp.component('LucidePhone', Phone) + nuxtApp.vueApp.component('LucideWrench', Wrench) + nuxtApp.vueApp.component('LucideChevronDown', ChevronDown) + nuxtApp.vueApp.component('LucideShip', Ship) + nuxtApp.vueApp.component('LucideAward', Award) + nuxtApp.vueApp.component('LucideUsers', Users) + nuxtApp.vueApp.component('LucideShieldCheck', ShieldCheck) + nuxtApp.vueApp.component('LucideCalendar', Calendar) + nuxtApp.vueApp.component('LucideCalendarCheck', CalendarCheck) + nuxtApp.vueApp.component('LucideMail', Mail) + nuxtApp.vueApp.component('LucideMapPin', MapPin) + nuxtApp.vueApp.component('LucideClock', Clock) + nuxtApp.vueApp.component('LucideFacebook', Facebook) + nuxtApp.vueApp.component('LucideInstagram', Instagram) + nuxtApp.vueApp.component('LucideTwitter', Twitter) + nuxtApp.vueApp.component('LucideCheck', Check) + nuxtApp.vueApp.component('LucideMenu', Menu) + nuxtApp.vueApp.component('LucideX', X) + nuxtApp.vueApp.component('LucideTruck', Truck) +}) + diff --git a/apps/website/public/Anodes.jpg b/apps/website/public/Anodes.jpg new file mode 100644 index 0000000..48b39d3 Binary files /dev/null and b/apps/website/public/Anodes.jpg differ diff --git a/apps/website/public/Calendar.jpg b/apps/website/public/Calendar.jpg new file mode 100644 index 0000000..bc22e3e Binary files /dev/null and b/apps/website/public/Calendar.jpg differ diff --git a/apps/website/public/ExtCleaning.jpg b/apps/website/public/ExtCleaning.jpg new file mode 100644 index 0000000..446faed Binary files /dev/null and b/apps/website/public/ExtCleaning.jpg differ diff --git a/apps/website/public/Foredeck.jpg b/apps/website/public/Foredeck.jpg new file mode 100644 index 0000000..b4b4c9c Binary files /dev/null and b/apps/website/public/Foredeck.jpg differ diff --git a/apps/website/public/HARBOR-SMITH-white.png b/apps/website/public/HARBOR-SMITH-white.png new file mode 100644 index 0000000..60c9730 Binary files /dev/null and b/apps/website/public/HARBOR-SMITH-white.png differ diff --git a/apps/website/public/HARBOR-SMITH_navy.png b/apps/website/public/HARBOR-SMITH_navy.png new file mode 100644 index 0000000..2fc22be Binary files /dev/null and b/apps/website/public/HARBOR-SMITH_navy.png differ diff --git a/apps/website/public/Helm.jpg b/apps/website/public/Helm.jpg new file mode 100644 index 0000000..b579f16 Binary files /dev/null and b/apps/website/public/Helm.jpg differ diff --git a/apps/website/public/Hull Clean Pricing.jpg b/apps/website/public/Hull Clean Pricing.jpg new file mode 100644 index 0000000..253ad3b Binary files /dev/null and b/apps/website/public/Hull Clean Pricing.jpg differ diff --git a/apps/website/public/Interior.jpg b/apps/website/public/Interior.jpg new file mode 100644 index 0000000..e4bd98a Binary files /dev/null and b/apps/website/public/Interior.jpg differ diff --git a/apps/website/public/Licensed.jpg b/apps/website/public/Licensed.jpg new file mode 100644 index 0000000..be4c9e1 Binary files /dev/null and b/apps/website/public/Licensed.jpg differ diff --git a/apps/website/public/QRCode-White-sm.png b/apps/website/public/QRCode-White-sm.png new file mode 100644 index 0000000..fa5dcd8 Binary files /dev/null and b/apps/website/public/QRCode-White-sm.png differ diff --git a/apps/website/public/QRCode-White.png b/apps/website/public/QRCode-White.png new file mode 100644 index 0000000..a1d2f4d Binary files /dev/null and b/apps/website/public/QRCode-White.png differ diff --git a/apps/website/public/QRCode.png b/apps/website/public/QRCode.png new file mode 100644 index 0000000..e4dd403 Binary files /dev/null and b/apps/website/public/QRCode.png differ diff --git a/apps/website/public/SpecialRequest.jpg b/apps/website/public/SpecialRequest.jpg new file mode 100644 index 0000000..b05c750 Binary files /dev/null and b/apps/website/public/SpecialRequest.jpg differ diff --git a/apps/website/public/Washdown.jpg b/apps/website/public/Washdown.jpg new file mode 100644 index 0000000..f0cebcb Binary files /dev/null and b/apps/website/public/Washdown.jpg differ diff --git a/apps/website/public/Washdown2.jpg b/apps/website/public/Washdown2.jpg new file mode 100644 index 0000000..87fc2df Binary files /dev/null and b/apps/website/public/Washdown2.jpg differ diff --git a/apps/website/public/Waxing.jpg b/apps/website/public/Waxing.jpg new file mode 100644 index 0000000..c3a881c Binary files /dev/null and b/apps/website/public/Waxing.jpg differ diff --git a/apps/website/public/_robots.txt b/apps/website/public/_robots.txt new file mode 100644 index 0000000..0ad279c --- /dev/null +++ b/apps/website/public/_robots.txt @@ -0,0 +1,2 @@ +User-Agent: * +Disallow: diff --git a/apps/website/public/diver_cleaning.jpg b/apps/website/public/diver_cleaning.jpg new file mode 100644 index 0000000..e6b651c Binary files /dev/null and b/apps/website/public/diver_cleaning.jpg differ diff --git a/apps/website/public/diver_cleaning_2.jpg b/apps/website/public/diver_cleaning_2.jpg new file mode 100644 index 0000000..2ea5176 Binary files /dev/null and b/apps/website/public/diver_cleaning_2.jpg differ diff --git a/apps/website/public/favicon.ico b/apps/website/public/favicon.ico new file mode 100644 index 0000000..18993ad Binary files /dev/null and b/apps/website/public/favicon.ico differ diff --git a/apps/website/public/golden_gate.jpg b/apps/website/public/golden_gate.jpg new file mode 100644 index 0000000..750571c Binary files /dev/null and b/apps/website/public/golden_gate.jpg differ diff --git a/apps/website/public/iStock-2189089654.jpg b/apps/website/public/iStock-2189089654.jpg new file mode 100644 index 0000000..eba7d06 Binary files /dev/null and b/apps/website/public/iStock-2189089654.jpg differ diff --git a/apps/website/public/iStock-504868014.jpg b/apps/website/public/iStock-504868014.jpg new file mode 100644 index 0000000..0d4a757 Binary files /dev/null and b/apps/website/public/iStock-504868014.jpg differ diff --git a/apps/website/public/iStock-923244752.jpg b/apps/website/public/iStock-923244752.jpg new file mode 100644 index 0000000..c24a2a2 Binary files /dev/null and b/apps/website/public/iStock-923244752.jpg differ diff --git a/apps/website/public/iStock-923244752b.jpg b/apps/website/public/iStock-923244752b.jpg new file mode 100644 index 0000000..e42bf1d Binary files /dev/null and b/apps/website/public/iStock-923244752b.jpg differ diff --git a/apps/website/public/kid_1.jpeg b/apps/website/public/kid_1.jpeg new file mode 100644 index 0000000..542f685 Binary files /dev/null and b/apps/website/public/kid_1.jpeg differ diff --git a/apps/website/public/leah_1.jpeg b/apps/website/public/leah_1.jpeg new file mode 100644 index 0000000..35eb246 Binary files /dev/null and b/apps/website/public/leah_1.jpeg differ diff --git a/apps/website/public/sausalito-boat-show-2024.jpg b/apps/website/public/sausalito-boat-show-2024.jpg new file mode 100644 index 0000000..a15c52e Binary files /dev/null and b/apps/website/public/sausalito-boat-show-2024.jpg differ diff --git a/apps/website/public/sf_bay_exposure.jpg b/apps/website/public/sf_bay_exposure.jpg new file mode 100644 index 0000000..750571c Binary files /dev/null and b/apps/website/public/sf_bay_exposure.jpg differ diff --git a/apps/website/tsconfig.json b/apps/website/tsconfig.json new file mode 100644 index 0000000..307b213 --- /dev/null +++ b/apps/website/tsconfig.json @@ -0,0 +1,18 @@ +{ + // https://nuxt.com/docs/guide/concepts/typescript + "files": [], + "references": [ + { + "path": "./.nuxt/tsconfig.app.json" + }, + { + "path": "./.nuxt/tsconfig.server.json" + }, + { + "path": "./.nuxt/tsconfig.shared.json" + }, + { + "path": "./.nuxt/tsconfig.node.json" + } + ] +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..01f304c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,306 @@ +version: '3.9' + +networks: + harborsmith: + driver: bridge + +volumes: + postgres_data: + minio_data: + keycloak_data: + directus_uploads: + redis_data: + +services: + # ===== FRONTEND ===== + web: + build: + context: ./apps/web + dockerfile: Dockerfile + args: + - NODE_ENV=production + container_name: harborsmith-web + restart: unless-stopped + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - NEXT_PUBLIC_API_URL=http://api:4000 + - NEXT_PUBLIC_SUPABASE_URL=${SUPABASE_URL} + - NEXT_PUBLIC_SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY} + - NEXT_PUBLIC_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY} + - NEXT_PUBLIC_KEYCLOAK_URL=http://keycloak:8080 + - NEXT_PUBLIC_KEYCLOAK_REALM=harborsmith + - NEXT_PUBLIC_KEYCLOAK_CLIENT_ID=harborsmith-web + depends_on: + - api + - keycloak + networks: + - harborsmith + + # ===== API GATEWAY ===== + api: + build: + context: ./apps/api + dockerfile: Dockerfile + container_name: harborsmith-api + restart: unless-stopped + ports: + - "4000:4000" + environment: + - NODE_ENV=production + - PORT=4000 + - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/harborsmith + - REDIS_URL=redis://redis:6379 + - KEYCLOAK_URL=http://keycloak:8080 + - KEYCLOAK_REALM=harborsmith + - KEYCLOAK_CLIENT_ID=harborsmith-api + - KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET} + - JWT_SECRET=${JWT_SECRET} + - CHARTER_SERVICE_URL=http://charter-service:5001 + - MAINTENANCE_SERVICE_URL=http://maintenance-service:5002 + - PAYMENT_SERVICE_URL=http://payment-service:5003 + - NOTIFICATION_SERVICE_URL=http://notification-service:5004 + depends_on: + - postgres + - redis + - keycloak + networks: + - harborsmith + + # ===== MICROSERVICES ===== + charter-service: + build: + context: ./services/charter + dockerfile: Dockerfile + container_name: harborsmith-charter + restart: unless-stopped + ports: + - "5001:5001" + environment: + - NODE_ENV=production + - PORT=5001 + - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/harborsmith + - REDIS_URL=redis://redis:6379 + depends_on: + - postgres + - redis + networks: + - harborsmith + + maintenance-service: + build: + context: ./services/maintenance + dockerfile: Dockerfile + container_name: harborsmith-maintenance + restart: unless-stopped + ports: + - "5002:5002" + environment: + - NODE_ENV=production + - PORT=5002 + - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/harborsmith + - REDIS_URL=redis://redis:6379 + depends_on: + - postgres + - redis + networks: + - harborsmith + + payment-service: + build: + context: ./services/payments + dockerfile: Dockerfile + container_name: harborsmith-payments + restart: unless-stopped + ports: + - "5003:5003" + environment: + - NODE_ENV=production + - PORT=5003 + - STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY} + - STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET} + - INVOICE_NINJA_URL=${INVOICE_NINJA_URL} + - INVOICE_NINJA_TOKEN=${INVOICE_NINJA_TOKEN} + - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/harborsmith + depends_on: + - postgres + networks: + - harborsmith + + notification-service: + build: + context: ./services/notifications + dockerfile: Dockerfile + container_name: harborsmith-notifications + restart: unless-stopped + ports: + - "5004:5004" + environment: + - NODE_ENV=production + - PORT=5004 + - SMTP_HOST=${POSTE_HOST} + - SMTP_PORT=${POSTE_PORT} + - SMTP_SECURE=${POSTE_SECURE} + - SMTP_USER=${POSTE_USER} + - SMTP_PASS=${POSTE_PASS} + - LISTMONK_URL=${LISTMONK_URL} + - LISTMONK_USER=${LISTMONK_USER} + - LISTMONK_PASS=${LISTMONK_PASS} + networks: + - harborsmith + + # ===== DATABASES & STORAGE ===== + postgres: + image: supabase/postgres:15.1.0.117 + container_name: harborsmith-db + restart: unless-stopped + ports: + - "5432:5432" + environment: + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=harborsmith + volumes: + - postgres_data:/var/lib/postgresql/data + - ./migrations:/docker-entrypoint-initdb.d:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - harborsmith + + redis: + image: redis:7-alpine + container_name: harborsmith-redis + restart: unless-stopped + ports: + - "6379:6379" + volumes: + - redis_data:/data + command: redis-server --appendonly yes + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - harborsmith + + # ===== AUTHENTICATION ===== + keycloak: + image: quay.io/keycloak/keycloak:23.0 + container_name: harborsmith-keycloak + restart: unless-stopped + ports: + - "8080:8080" + environment: + - KEYCLOAK_ADMIN=${KEYCLOAK_ADMIN} + - KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN_PASSWORD} + - KC_DB=postgres + - KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak + - KC_DB_USERNAME=postgres + - KC_DB_PASSWORD=${POSTGRES_PASSWORD} + - KC_HOSTNAME_STRICT=false + - KC_HTTP_ENABLED=true + - KC_HEALTH_ENABLED=true + command: start-dev + volumes: + - keycloak_data:/opt/keycloak/data + depends_on: + - postgres + networks: + - harborsmith + + # ===== FILE STORAGE ===== + minio: + image: minio/minio:latest + container_name: harborsmith-minio + restart: unless-stopped + ports: + - "9000:9000" + - "9001:9001" + environment: + - MINIO_ROOT_USER=${MINIO_ROOT_USER} + - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD} + - MINIO_BROWSER_REDIRECT_URL=http://localhost:9001 + volumes: + - minio_data:/data + command: server /data --console-address ":9001" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 30s + timeout: 20s + retries: 3 + networks: + - harborsmith + + createbuckets: + image: minio/mc:latest + container_name: harborsmith-minio-setup + depends_on: + - minio + entrypoint: > + /bin/sh -c " + /usr/bin/mc config host add harborsmith http://minio:9000 ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD}; + /usr/bin/mc mb harborsmith/documents; + /usr/bin/mc mb harborsmith/images; + /usr/bin/mc mb harborsmith/directus; + /usr/bin/mc policy set public harborsmith/images; + exit 0; + " + networks: + - harborsmith + + # ===== CMS ===== + directus: + image: directus/directus:10.8 + container_name: harborsmith-cms + restart: unless-stopped + ports: + - "8055:8055" + environment: + - KEY=${DIRECTUS_KEY} + - SECRET=${DIRECTUS_SECRET} + - DB_CLIENT=pg + - DB_HOST=postgres + - DB_PORT=5432 + - DB_DATABASE=directus + - DB_USER=postgres + - DB_PASSWORD=${POSTGRES_PASSWORD} + - ADMIN_EMAIL=${DIRECTUS_ADMIN_EMAIL} + - ADMIN_PASSWORD=${DIRECTUS_ADMIN_PASSWORD} + - PUBLIC_URL=http://localhost:8055 + - STORAGE_LOCATIONS=s3 + - STORAGE_S3_DRIVER=s3 + - STORAGE_S3_ENDPOINT=http://minio:9000 + - STORAGE_S3_BUCKET=directus + - STORAGE_S3_REGION=us-east-1 + - STORAGE_S3_KEY=${MINIO_ROOT_USER} + - STORAGE_S3_SECRET=${MINIO_ROOT_PASSWORD} + volumes: + - directus_uploads:/directus/uploads + depends_on: + - postgres + - minio + networks: + - harborsmith + + # ===== ANALYTICS ===== + umami: + image: ghcr.io/umami-software/umami:postgresql-latest + container_name: harborsmith-analytics + restart: unless-stopped + ports: + - "3001:3000" + environment: + - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/umami + - DATABASE_TYPE=postgresql + - HASH_SALT=${UMAMI_HASH_SALT} + - TRACKER_SCRIPT_NAME=script.js + - DISABLE_UPDATES=true + depends_on: + - postgres + networks: + - harborsmith diff --git a/docs/GITEA_DOCKER_WORKFLOW.md b/docs/GITEA_DOCKER_WORKFLOW.md new file mode 100644 index 0000000..fbc84f0 --- /dev/null +++ b/docs/GITEA_DOCKER_WORKFLOW.md @@ -0,0 +1,829 @@ +# HarborSmith Gitea Docker Workflow +## Automated Build & Deployment Pipeline + +**Version:** 1.0 +**Date:** September 2025 +**Purpose:** Gitea repository setup with automated Docker image building and deployment + +--- + +## ๐Ÿ“‹ Overview + +This guide configures HarborSmith to work with your Gitea instance for: +- Source control management +- Automated Docker image building +- Container registry management +- Automated deployment to production + +--- + +## ๐Ÿ—๏ธ Repository Structure + +```bash +harborsmith/ # Main repository in Gitea +โ”œโ”€โ”€ .gitea/ # Gitea-specific configuration +โ”‚ โ””โ”€โ”€ workflows/ # Gitea Actions (CI/CD) +โ”‚ โ”œโ”€โ”€ build.yaml # Build Docker images on push +โ”‚ โ”œโ”€โ”€ test.yaml # Run tests +โ”‚ โ””โ”€โ”€ deploy.yaml # Deploy to production +โ”œโ”€โ”€ .dockerignore # Files to exclude from Docker builds +โ”œโ”€โ”€ docker-compose.yml # Production orchestration +โ”œโ”€โ”€ docker-compose.dev.yml # Development overrides +โ”œโ”€โ”€ docker-compose.build.yml # Build configuration +โ”œโ”€โ”€ Makefile # Build and deploy commands +โ””โ”€โ”€ [rest of project structure as defined] +``` + +--- + +## ๐Ÿ”ง Gitea Actions Workflow + +### Build Workflow (.gitea/workflows/build.yaml) +```yaml +name: Build and Push Docker Images +on: + push: + branches: [main, develop] + tags: + - 'v*' + pull_request: + branches: [main] + +jobs: + build-web: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Gitea Registry + uses: docker/login-action@v2 + with: + registry: gitea.yourdomain.com + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: gitea.yourdomain.com/harborsmith/web + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha,prefix={{branch}}- + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push Web + uses: docker/build-push-action@v4 + with: + context: ./apps/web + file: ./apps/web/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=registry,ref=gitea.yourdomain.com/harborsmith/web:buildcache + cache-to: type=registry,ref=gitea.yourdomain.com/harborsmith/web:buildcache,mode=max + build-args: | + BUILDKIT_INLINE_CACHE=1 + NODE_ENV=production + + build-api: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Gitea Registry + uses: docker/login-action@v2 + with: + registry: gitea.yourdomain.com + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: gitea.yourdomain.com/harborsmith/api + + - name: Build and push API + uses: docker/build-push-action@v4 + with: + context: ./apps/api + file: ./apps/api/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=registry,ref=gitea.yourdomain.com/harborsmith/api:buildcache + cache-to: type=registry,ref=gitea.yourdomain.com/harborsmith/api:buildcache,mode=max + + build-services: + runs-on: ubuntu-latest + strategy: + matrix: + service: [charter, maintenance, payments, notifications] + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Gitea Registry + uses: docker/login-action@v2 + with: + registry: gitea.yourdomain.com + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: gitea.yourdomain.com/harborsmith/${{ matrix.service }} + + - name: Build and push ${{ matrix.service }} + uses: docker/build-push-action@v4 + with: + context: ./services/${{ matrix.service }} + file: ./services/${{ matrix.service }}/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} +``` + +--- + +## ๐Ÿณ Optimized Dockerfiles + +### Multi-stage Dockerfile for Next.js (apps/web/Dockerfile) +```dockerfile +# Dependencies stage +FROM node:20-alpine AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Copy package files +COPY package.json pnpm-lock.yaml* ./ +RUN corepack enable pnpm && pnpm i --frozen-lockfile + +# Builder stage +FROM node:20-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Build arguments for environment variables +ARG NEXT_PUBLIC_API_URL +ARG NEXT_PUBLIC_SUPABASE_URL +ARG NEXT_PUBLIC_SUPABASE_ANON_KEY +ARG NEXT_PUBLIC_STRIPE_PUBLIC_KEY +ARG NEXT_PUBLIC_KEYCLOAK_URL +ARG NEXT_PUBLIC_KEYCLOAK_REALM +ARG NEXT_PUBLIC_KEYCLOAK_CLIENT_ID + +ENV NEXT_TELEMETRY_DISABLED 1 +RUN corepack enable pnpm && pnpm build + +# Runner stage +FROM node:20-alpine AS runner +WORKDIR /app + +ENV NODE_ENV production +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy built application +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 +ENV PORT 3000 +ENV HOSTNAME "0.0.0.0" + +CMD ["node", "server.js"] +``` + +### Optimized API Dockerfile (apps/api/Dockerfile) +```dockerfile +# Build stage +FROM node:20-alpine AS builder +RUN apk add --no-cache libc6-compat +WORKDIR /app + +COPY package.json pnpm-lock.yaml* ./ +RUN corepack enable pnpm && pnpm i --frozen-lockfile + +COPY . . +RUN pnpm build + +# Production stage +FROM node:20-alpine AS runner +RUN apk add --no-cache libc6-compat +WORKDIR /app + +ENV NODE_ENV production + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 apiuser + +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/package.json ./package.json + +USER apiuser + +EXPOSE 4000 + +CMD ["node", "dist/server.js"] +``` + +### Microservice Dockerfile Template (services/*/Dockerfile) +```dockerfile +FROM node:20-alpine AS builder +WORKDIR /app + +COPY package*.json ./ +COPY pnpm-lock.yaml* ./ +RUN corepack enable pnpm && pnpm i --frozen-lockfile + +COPY . . +RUN pnpm build + +FROM node:20-alpine +WORKDIR /app + +RUN addgroup -g 1001 -S nodejs +RUN adduser -S nodejs -u 1001 + +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/node_modules ./node_modules + +USER nodejs + +EXPOSE 5001 + +CMD ["node", "dist/index.js"] +``` + +--- + +## ๐Ÿ“ฆ Production docker-compose.yml (Using Gitea Registry) + +```yaml +version: '3.9' + +networks: + harborsmith: + driver: bridge + +volumes: + postgres_data: + minio_data: + keycloak_data: + redis_data: + +services: + # ===== FRONTEND ===== + web: + image: gitea.yourdomain.com/harborsmith/web:latest + container_name: harborsmith-web + restart: unless-stopped + ports: + - "3000:3000" + env_file: + - .env.production + depends_on: + - api + - keycloak + networks: + - harborsmith + + # ===== API GATEWAY ===== + api: + image: gitea.yourdomain.com/harborsmith/api:latest + container_name: harborsmith-api + restart: unless-stopped + ports: + - "4000:4000" + env_file: + - .env.production + depends_on: + - postgres + - redis + - keycloak + networks: + - harborsmith + + # ===== MICROSERVICES ===== + charter-service: + image: gitea.yourdomain.com/harborsmith/charter:latest + container_name: harborsmith-charter + restart: unless-stopped + env_file: + - .env.production + depends_on: + - postgres + - redis + networks: + - harborsmith + + maintenance-service: + image: gitea.yourdomain.com/harborsmith/maintenance:latest + container_name: harborsmith-maintenance + restart: unless-stopped + env_file: + - .env.production + depends_on: + - postgres + - redis + networks: + - harborsmith + + payment-service: + image: gitea.yourdomain.com/harborsmith/payments:latest + container_name: harborsmith-payments + restart: unless-stopped + env_file: + - .env.production + depends_on: + - postgres + networks: + - harborsmith + + notification-service: + image: gitea.yourdomain.com/harborsmith/notifications:latest + container_name: harborsmith-notifications + restart: unless-stopped + env_file: + - .env.production + networks: + - harborsmith + + # ===== INFRASTRUCTURE SERVICES ===== + # (These use public images, not built from source) + postgres: + image: supabase/postgres:15.1.0.117 + container_name: harborsmith-db + restart: unless-stopped + ports: + - "5432:5432" + env_file: + - .env.production + volumes: + - postgres_data:/var/lib/postgresql/data + - ./migrations:/docker-entrypoint-initdb.d:ro + networks: + - harborsmith + + redis: + image: redis:7-alpine + container_name: harborsmith-redis + restart: unless-stopped + ports: + - "6379:6379" + volumes: + - redis_data:/data + networks: + - harborsmith + + keycloak: + image: quay.io/keycloak/keycloak:23.0 + container_name: harborsmith-keycloak + restart: unless-stopped + ports: + - "8080:8080" + env_file: + - .env.production + volumes: + - keycloak_data:/opt/keycloak/data + depends_on: + - postgres + networks: + - harborsmith + + minio: + image: minio/minio:latest + container_name: harborsmith-minio + restart: unless-stopped + ports: + - "9000:9000" + - "9001:9001" + env_file: + - .env.production + volumes: + - minio_data:/data + command: server /data --console-address ":9001" + networks: + - harborsmith + + # Additional services as defined in main implementation plan... +``` + +--- + +## ๐Ÿ”„ Deployment Workflow + +### 1. Initial Setup on Production Server +```bash +# On your production server +# 1. Install Docker and Docker Compose +curl -fsSL https://get.docker.com -o get-docker.sh +sh get-docker.sh +apt-get install docker-compose-plugin + +# 2. Create project directory +mkdir -p /opt/harborsmith +cd /opt/harborsmith + +# 3. Login to Gitea Registry +docker login gitea.yourdomain.com + +# 4. Clone deployment files (not full source) +git clone https://gitea.yourdomain.com/harborsmith/deployment.git . +``` + +### 2. Deployment Script (deploy.sh) +```bash +#!/bin/bash +# HarborSmith Deployment Script + +set -e + +echo "๐Ÿš€ Starting HarborSmith deployment..." + +# Configuration +GITEA_REGISTRY="gitea.yourdomain.com" +PROJECT="harborsmith" +ENVIRONMENT=${1:-production} + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + echo -e "${GREEN}โœ“${NC} $1" +} + +print_error() { + echo -e "${RED}โœ—${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}โš ${NC} $1" +} + +# 1. Login to Gitea Registry +print_status "Logging into Gitea Registry..." +docker login ${GITEA_REGISTRY} + +# 2. Pull latest images +print_status "Pulling latest images..." +SERVICES="web api charter maintenance payments notifications" +for SERVICE in $SERVICES; do + print_status "Pulling ${SERVICE}..." + docker pull ${GITEA_REGISTRY}/${PROJECT}/${SERVICE}:latest +done + +# 3. Backup database (optional but recommended) +if [ "$ENVIRONMENT" = "production" ]; then + print_warning "Backing up database..." + docker exec harborsmith-db pg_dump -U postgres harborsmith > backup-$(date +%Y%m%d-%H%M%S).sql +fi + +# 4. Stop running containers +print_status "Stopping current containers..." +docker-compose down + +# 5. Start new containers +print_status "Starting new containers..." +docker-compose up -d + +# 6. Run migrations +print_status "Running database migrations..." +docker exec harborsmith-db psql -U postgres -d harborsmith -f /docker-entrypoint-initdb.d/migrate.sql + +# 7. Health checks +print_status "Performing health checks..." +sleep 10 + +# Check if services are running +HEALTH_CHECK_FAILED=0 +for SERVICE in $SERVICES; do + if docker ps | grep -q "harborsmith-${SERVICE}"; then + print_status "${SERVICE} is running" + else + print_error "${SERVICE} is not running" + HEALTH_CHECK_FAILED=1 + fi +done + +# 8. Cleanup old images +print_status "Cleaning up old images..." +docker image prune -f + +if [ $HEALTH_CHECK_FAILED -eq 0 ]; then + print_status "Deployment completed successfully! ๐ŸŽ‰" + echo "" + echo "Services available at:" + echo " - Web: http://localhost:3000" + echo " - API: http://localhost:4000" + echo " - Keycloak: http://localhost:8080" + echo " - MinIO: http://localhost:9001" +else + print_error "Deployment completed with errors. Please check the logs." + exit 1 +fi +``` + +--- + +## ๐Ÿ› ๏ธ Makefile for Common Operations + +```makefile +# HarborSmith Makefile +.PHONY: help build push deploy logs restart clean + +# Variables +GITEA_REGISTRY := gitea.yourdomain.com +PROJECT := harborsmith +VERSION := $(shell git describe --tags --always --dirty) +BRANCH := $(shell git rev-parse --abbrev-ref HEAD) + +help: ## Show this help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +build: ## Build all Docker images locally + docker-compose -f docker-compose.build.yml build + +build-web: ## Build web service + docker build -t $(GITEA_REGISTRY)/$(PROJECT)/web:$(VERSION) ./apps/web + +build-api: ## Build API service + docker build -t $(GITEA_REGISTRY)/$(PROJECT)/api:$(VERSION) ./apps/api + +push: ## Push all images to Gitea registry + docker push $(GITEA_REGISTRY)/$(PROJECT)/web:$(VERSION) + docker push $(GITEA_REGISTRY)/$(PROJECT)/api:$(VERSION) + docker push $(GITEA_REGISTRY)/$(PROJECT)/charter:$(VERSION) + docker push $(GITEA_REGISTRY)/$(PROJECT)/maintenance:$(VERSION) + docker push $(GITEA_REGISTRY)/$(PROJECT)/payments:$(VERSION) + docker push $(GITEA_REGISTRY)/$(PROJECT)/notifications:$(VERSION) + +deploy: ## Deploy to production + ssh production-server 'cd /opt/harborsmith && ./deploy.sh' + +deploy-staging: ## Deploy to staging + ssh staging-server 'cd /opt/harborsmith && ./deploy.sh staging' + +logs: ## Show logs from all services + docker-compose logs -f + +logs-web: ## Show logs from web service + docker-compose logs -f web + +restart: ## Restart all services + docker-compose restart + +restart-web: ## Restart web service + docker-compose restart web + +clean: ## Clean up Docker resources + docker system prune -f + docker volume prune -f + +backup: ## Backup database + docker exec harborsmith-db pg_dump -U postgres harborsmith > backups/backup-$(shell date +%Y%m%d-%H%M%S).sql + +restore: ## Restore database from latest backup + docker exec -i harborsmith-db psql -U postgres harborsmith < $(shell ls -t backups/*.sql | head -1) + +dev: ## Start development environment + docker-compose -f docker-compose.dev.yml up + +test: ## Run tests in containers + docker-compose -f docker-compose.test.yml up --abort-on-container-exit + +migrate: ## Run database migrations + docker exec harborsmith-db psql -U postgres -d harborsmith -f /docker-entrypoint-initdb.d/migrate.sql +``` + +--- + +## ๐Ÿ” Gitea Repository Secrets + +Configure these secrets in your Gitea repository settings: + +```yaml +REGISTRY_USERNAME: harborsmith-bot +REGISTRY_PASSWORD: [secure-token] +PRODUCTION_HOST: production.harborsmith.com +STAGING_HOST: staging.harborsmith.com +SSH_PRIVATE_KEY: [deployment-key] +``` + +--- + +## ๐Ÿ“ .dockerignore + +```dockerignore +# Dependencies +node_modules +npm-debug.log +yarn-error.log +.pnpm-store + +# Next.js +.next +out +build +dist + +# Testing +coverage +.nyc_output + +# Environment +.env +.env.* +!.env.example + +# Git +.git +.gitignore +.gitea + +# Documentation +*.md +docs + +# IDE +.vscode +.idea +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Development +.docker +docker-compose.dev.yml +docker-compose.test.yml + +# Temporary files +tmp +temp +*.tmp +``` + +--- + +## ๐Ÿš€ Complete Workflow Example + +### 1. Developer pushes code to Gitea +```bash +git add . +git commit -m "feat: add vessel booking feature" +git push origin develop +``` + +### 2. Gitea Actions automatically: +- Runs tests +- Builds Docker images +- Tags with branch name and commit SHA +- Pushes to Gitea container registry + +### 3. Deploy to staging +```bash +make deploy-staging +# OR manually: +ssh staging-server +cd /opt/harborsmith +docker-compose pull +docker-compose up -d +``` + +### 4. Deploy to production (after testing) +```bash +git checkout main +git merge develop +git tag v1.2.0 +git push origin main --tags + +# Gitea Actions builds production images +make deploy +``` + +--- + +## ๐Ÿ“Š Monitoring Container Health + +### Health Check Script (healthcheck.sh) +```bash +#!/bin/bash +# Container health monitoring + +SERVICES=("web" "api" "charter" "maintenance" "payments" "notifications") +WEBHOOK_URL="https://your-monitoring-webhook.com" + +for service in "${SERVICES[@]}"; do + if ! docker exec harborsmith-${service} curl -f http://localhost:${PORT}/health > /dev/null 2>&1; then + curl -X POST ${WEBHOOK_URL} \ + -H "Content-Type: application/json" \ + -d "{\"service\": \"${service}\", \"status\": \"unhealthy\", \"timestamp\": \"$(date -Iseconds)\"}" + + # Attempt restart + docker-compose restart ${service} + fi +done +``` + +--- + +## ๐Ÿ”„ Rolling Updates + +For zero-downtime deployments: + +```yaml +# docker-compose.production.yml +services: + web: + image: gitea.yourdomain.com/harborsmith/web:latest + deploy: + replicas: 2 + update_config: + parallelism: 1 + delay: 10s + order: start-first + restart_policy: + condition: any + delay: 5s + max_attempts: 3 +``` + +--- + +## โœ… Deployment Checklist + +- [ ] Gitea repository created +- [ ] Container registry enabled in Gitea +- [ ] Gitea Actions configured +- [ ] Secrets added to repository +- [ ] Production server has Docker installed +- [ ] Registry credentials configured on server +- [ ] Environment files prepared +- [ ] SSL certificates ready +- [ ] Database backups scheduled +- [ ] Monitoring configured +- [ ] Rollback plan documented + +--- + +## ๐Ÿ†˜ Troubleshooting + +### Common Issues + +1. **Image pull authentication failed** +```bash +docker logout gitea.yourdomain.com +docker login gitea.yourdomain.com +``` + +2. **Container fails to start** +```bash +docker logs harborsmith-web +docker-compose down +docker-compose up -d +``` + +3. **Database connection issues** +```bash +docker exec harborsmith-db pg_isready +docker-compose restart postgres +``` + +4. **Build cache issues** +```bash +docker builder prune -a +docker-compose build --no-cache +``` + +--- + +*This workflow ensures automated, reliable deployments from your Gitea repository to production Docker containers.* diff --git a/docs/HARBORSMITH_IMPLEMENTATION_PLAN.md b/docs/HARBORSMITH_IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000..e981301 --- /dev/null +++ b/docs/HARBORSMITH_IMPLEMENTATION_PLAN.md @@ -0,0 +1,1415 @@ +# HarborSmith Platform Implementation Plan +## Complete Technical Guide for Production Development + +**Version:** 1.0 +**Date:** September 2025 +**Status:** Ready for Development + +--- + +## ๐Ÿ“‹ Executive Summary + +HarborSmith is a comprehensive marine services platform bridging yacht owners and maritime service providers through two primary verticals: **Maintenance Services** and **Charter Services**. This document provides the complete technical implementation plan for migrating from HTML mockups to a production-ready platform using modern, scalable architecture without cutting corners. + +### Key Objectives +- Build a robust, scalable platform from day one +- Implement microservices architecture for future growth +- Ensure enterprise-grade security with SSO +- Enable real-time capabilities for enhanced UX +- Maintain SEO optimization through SSR +- Deploy rapidly without compromising quality + +--- + +## ๐Ÿ—๏ธ Technology Stack + +### Core Technologies +```yaml +# Frontend +Framework: Next.js 14 (App Router) +Language: TypeScript +Styling: TailwindCSS + Shadcn/UI +State Management: Zustand + TanStack Query +Forms: React Hook Form + Zod +SEO: Server-Side Rendering (SSR) + +# Backend +Architecture: Microservices +Runtime: Node.js +Framework: Fastify +API Design: RESTful +Validation: Zod +Documentation: OpenAPI/Swagger + +# Database +Primary: Supabase (PostgreSQL) +Cache: Redis +Real-time: Supabase Realtime +File Storage: MinIO S3 + +# Authentication +SSO Provider: Keycloak +Session Management: JWT + Refresh Tokens +Authorization: RBAC with Casbin + +# Infrastructure +Containerization: Docker +Reverse Proxy: Nginx +Orchestration: Docker Compose โ†’ Kubernetes (future) +Monitoring: Umami Analytics + +# Third-Party Services +Payments: Stripe +Invoicing: InvoiceNinja API +Email: Poste.io (SMTP) +Marketing: Listmonk +Maps: Mapbox +Weather: OpenWeatherMap +CMS: Directus +``` + +--- + +## ๐Ÿ“ Project Structure + +```bash +harborsmith-platform/ +โ”œโ”€โ”€ docker-compose.yml # Production orchestration +โ”œโ”€โ”€ docker-compose.dev.yml # Development overrides +โ”œโ”€โ”€ .env.example # Environment variables template +โ”œโ”€โ”€ Makefile # Common commands and shortcuts +โ”œโ”€โ”€ README.md # Project overview +โ”‚ +โ”œโ”€โ”€ docs/ # Documentation +โ”‚ โ”œโ”€โ”€ HARBORSMITH_IMPLEMENTATION_PLAN.md (this file) +โ”‚ โ”œโ”€โ”€ API_DOCUMENTATION.md +โ”‚ โ”œโ”€โ”€ DEPLOYMENT_GUIDE.md +โ”‚ โ””โ”€โ”€ TROUBLESHOOTING.md +โ”‚ +โ”œโ”€โ”€ nginx/ # Reverse proxy configuration +โ”‚ โ”œโ”€โ”€ nginx.conf # Main Nginx config +โ”‚ โ”œโ”€โ”€ ssl/ # SSL certificates +โ”‚ โ””โ”€โ”€ sites/ +โ”‚ โ”œโ”€โ”€ app.conf # Frontend routing +โ”‚ โ”œโ”€โ”€ api.conf # API gateway routing +โ”‚ โ””โ”€โ”€ services.conf # Microservices routing +โ”‚ +โ”œโ”€โ”€ apps/ +โ”‚ โ”œโ”€โ”€ web/ # Next.js Frontend Application +โ”‚ โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ app/ # App Router pages +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ (public)/ # Public routes +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ page.tsx # Homepage +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ charter/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ page.tsx +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ booking/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ [step]/page.tsx +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ components/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ maintenance/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ page.tsx +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ booking/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ about/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ contact/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ faq/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ (auth)/ # Protected routes +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ layout.tsx +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ dashboard/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ page.tsx +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ charter/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ maintenance/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ vessels/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ bookings/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ documents/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ invoices/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ profile/ +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ api/ # API route handlers +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ auth/[...nextauth]/route.ts +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ webhooks/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ components/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ui/ # Shadcn components +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ layout/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Navigation.tsx +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Footer.tsx +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ ThemeSwitcher.tsx +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ home/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ charter/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ maintenance/ +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ shared/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ lib/ +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ auth/ # Keycloak integration +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ db/ # Supabase client +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ api/ # API client +โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ stripe/ # Payment processing +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ utils/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ hooks/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ styles/ +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ globals.css +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ types/ +โ”‚ โ”‚ โ”œโ”€โ”€ public/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ images/ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ fonts/ +โ”‚ โ”‚ โ”œโ”€โ”€ Dockerfile +โ”‚ โ”‚ โ”œโ”€โ”€ next.config.js +โ”‚ โ”‚ โ”œโ”€โ”€ tailwind.config.js +โ”‚ โ”‚ โ”œโ”€โ”€ tsconfig.json +โ”‚ โ”‚ โ””โ”€โ”€ package.json +โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€ api/ # API Gateway +โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”œโ”€โ”€ server.ts # Main server file +โ”‚ โ”‚ โ”œโ”€โ”€ routes/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ index.ts +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ charter.routes.ts +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ maintenance.routes.ts +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ payment.routes.ts +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ user.routes.ts +โ”‚ โ”‚ โ”œโ”€โ”€ middleware/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ auth.middleware.ts +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ cors.middleware.ts +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ rate-limit.middleware.ts +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ validation.middleware.ts +โ”‚ โ”‚ โ”œโ”€โ”€ services/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ service.discovery.ts +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ circuit-breaker.ts +โ”‚ โ”‚ โ””โ”€โ”€ utils/ +โ”‚ โ”œโ”€โ”€ Dockerfile +โ”‚ โ”œโ”€โ”€ tsconfig.json +โ”‚ โ””โ”€โ”€ package.json +โ”‚ +โ”œโ”€โ”€ services/ # Microservices +โ”‚ โ”œโ”€โ”€ charter/ +โ”‚ โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ index.ts +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ controllers/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ models/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ repositories/ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ services/ +โ”‚ โ”‚ โ”œโ”€โ”€ tests/ +โ”‚ โ”‚ โ”œโ”€โ”€ Dockerfile +โ”‚ โ”‚ โ””โ”€โ”€ package.json +โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€ maintenance/ +โ”‚ โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ index.ts +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ controllers/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ models/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ repositories/ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ services/ +โ”‚ โ”‚ โ”œโ”€โ”€ tests/ +โ”‚ โ”‚ โ”œโ”€โ”€ Dockerfile +โ”‚ โ”‚ โ””โ”€โ”€ package.json +โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€ payments/ +โ”‚ โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ index.ts +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ stripe/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ invoice-ninja/ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ webhooks/ +โ”‚ โ”‚ โ”œโ”€โ”€ Dockerfile +โ”‚ โ”‚ โ””โ”€โ”€ package.json +โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€ notifications/ +โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”œโ”€โ”€ index.ts +โ”‚ โ”‚ โ”œโ”€โ”€ email/ +โ”‚ โ”‚ โ”œโ”€โ”€ sms/ +โ”‚ โ”‚ โ””โ”€โ”€ templates/ +โ”‚ โ”œโ”€โ”€ Dockerfile +โ”‚ โ””โ”€โ”€ package.json +โ”‚ +โ”œโ”€โ”€ packages/ # Shared packages (monorepo) +โ”‚ โ”œโ”€โ”€ types/ # TypeScript type definitions +โ”‚ โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ models.ts +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ api.ts +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ enums.ts +โ”‚ โ”‚ โ””โ”€โ”€ package.json +โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€ utils/ # Shared utilities +โ”‚ โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ dates.ts +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ validation.ts +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ formatters.ts +โ”‚ โ”‚ โ””โ”€โ”€ package.json +โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€ config/ # Shared configurations +โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”œโ”€โ”€ theme.ts +โ”‚ โ”‚ โ”œโ”€โ”€ constants.ts +โ”‚ โ”‚ โ””โ”€โ”€ api-routes.ts +โ”‚ โ””โ”€โ”€ package.json +โ”‚ +โ”œโ”€โ”€ migrations/ # Database migrations +โ”‚ โ”œโ”€โ”€ 001_initial_schema.sql +โ”‚ โ”œโ”€โ”€ 002_create_organizations.sql +โ”‚ โ”œโ”€โ”€ 003_create_users.sql +โ”‚ โ”œโ”€โ”€ 004_create_vessels.sql +โ”‚ โ”œโ”€โ”€ 005_create_bookings.sql +โ”‚ โ”œโ”€โ”€ 006_create_services.sql +โ”‚ โ”œโ”€โ”€ 007_create_invoices.sql +โ”‚ โ””โ”€โ”€ seed_data.sql +โ”‚ +โ””โ”€โ”€ scripts/ # Utility scripts + โ”œโ”€โ”€ setup.sh # Initial project setup + โ”œโ”€โ”€ migrate-mockups.js # Convert HTML mockups to React + โ”œโ”€โ”€ seed-database.js # Populate with demo data + โ””โ”€โ”€ deploy.sh # Deployment script +``` + +--- + +## ๐Ÿ—„๏ธ Database Architecture + +### Multi-Tenant Strategy: Row-Level Security with Shared Database + +```sql +-- Core schema design +CREATE SCHEMA IF NOT EXISTS public; +CREATE SCHEMA IF NOT EXISTS maintenance; +CREATE SCHEMA IF NOT EXISTS charter; + +-- Organizations table (tenants) +CREATE TABLE public.organizations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(255) NOT NULL, + type VARCHAR(20) CHECK (type IN ('maintenance', 'charter', 'both')), + subscription_tier VARCHAR(50) DEFAULT 'basic', + settings JSONB DEFAULT '{}', + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Users table with organization relationship +CREATE TABLE public.users ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + organization_id UUID REFERENCES public.organizations(id), + email VARCHAR(255) UNIQUE NOT NULL, + keycloak_id VARCHAR(255) UNIQUE, + role VARCHAR(50) NOT NULL, + profile JSONB DEFAULT '{}', + preferences JSONB DEFAULT '{}', + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Vessels table +CREATE TABLE public.vessels ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + organization_id UUID REFERENCES public.organizations(id), + name VARCHAR(255) NOT NULL, + type VARCHAR(50), + make VARCHAR(100), + model VARCHAR(100), + year INTEGER, + length_ft DECIMAL(5,2), + capacity INTEGER, + specifications JSONB DEFAULT '{}', + health_score INTEGER DEFAULT 100, + status VARCHAR(50) DEFAULT 'active', + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Charter bookings +CREATE TABLE charter.bookings ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + organization_id UUID REFERENCES public.organizations(id), + vessel_id UUID REFERENCES public.vessels(id), + user_id UUID REFERENCES public.users(id), + booking_date DATE NOT NULL, + start_time TIME NOT NULL, + duration_hours INTEGER NOT NULL, + guest_count INTEGER NOT NULL, + total_price DECIMAL(10,2), + status VARCHAR(50) DEFAULT 'pending', + payment_intent_id VARCHAR(255), + special_requests TEXT, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Maintenance work orders +CREATE TABLE maintenance.work_orders ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + organization_id UUID REFERENCES public.organizations(id), + vessel_id UUID REFERENCES public.vessels(id), + user_id UUID REFERENCES public.users(id), + service_type VARCHAR(100) NOT NULL, + priority VARCHAR(20) CHECK (priority IN ('emergency', 'urgent', 'routine')), + scheduled_date DATE, + description TEXT, + status VARCHAR(50) DEFAULT 'pending', + estimated_cost DECIMAL(10,2), + actual_cost DECIMAL(10,2), + technician_notes TEXT, + completed_at TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Documents table +CREATE TABLE public.documents ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + organization_id UUID REFERENCES public.organizations(id), + entity_type VARCHAR(50) NOT NULL, + entity_id UUID NOT NULL, + name VARCHAR(255) NOT NULL, + type VARCHAR(100), + s3_key VARCHAR(500) NOT NULL, + size_bytes BIGINT, + metadata JSONB DEFAULT '{}', + uploaded_by UUID REFERENCES public.users(id), + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Invoices table +CREATE TABLE public.invoices ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + organization_id UUID REFERENCES public.organizations(id), + invoice_number VARCHAR(50) UNIQUE NOT NULL, + type VARCHAR(20) CHECK (type IN ('charter', 'maintenance')), + booking_id UUID, + work_order_id UUID, + amount DECIMAL(10,2) NOT NULL, + status VARCHAR(50) DEFAULT 'pending', + invoice_ninja_id VARCHAR(255), + due_date DATE, + paid_at TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Enable Row Level Security +ALTER TABLE public.organizations ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.users ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.vessels ENABLE ROW LEVEL SECURITY; +ALTER TABLE charter.bookings ENABLE ROW LEVEL SECURITY; +ALTER TABLE maintenance.work_orders ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.documents ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.invoices ENABLE ROW LEVEL SECURITY; + +-- Create RLS policies +CREATE POLICY tenant_isolation_organizations ON public.organizations + USING (id = current_setting('app.current_organization')::UUID); + +CREATE POLICY tenant_isolation_users ON public.users + USING (organization_id = current_setting('app.current_organization')::UUID); + +CREATE POLICY tenant_isolation_vessels ON public.vessels + USING (organization_id = current_setting('app.current_organization')::UUID); + +-- Indexes for performance +CREATE INDEX idx_vessels_org_id ON public.vessels(organization_id); +CREATE INDEX idx_bookings_date ON charter.bookings(booking_date); +CREATE INDEX idx_work_orders_status ON maintenance.work_orders(status); +CREATE INDEX idx_invoices_status ON public.invoices(status); +CREATE INDEX idx_documents_entity ON public.documents(entity_type, entity_id); +``` + +--- + +## ๐Ÿ”Œ API Design + +### RESTful API Endpoints + +```typescript +// API Routes Structure +const API_ROUTES = { + // Authentication + 'POST /api/auth/login': 'User login with Keycloak', + 'POST /api/auth/logout': 'User logout', + 'POST /api/auth/refresh': 'Refresh JWT token', + 'GET /api/auth/me': 'Get current user', + + // Organizations + 'GET /api/organizations/:id': 'Get organization details', + 'PUT /api/organizations/:id': 'Update organization', + 'GET /api/organizations/:id/users': 'List organization users', + + // Vessels + 'GET /api/vessels': 'List vessels', + 'POST /api/vessels': 'Create vessel', + 'GET /api/vessels/:id': 'Get vessel details', + 'PUT /api/vessels/:id': 'Update vessel', + 'DELETE /api/vessels/:id': 'Delete vessel', + + // Charter Service + 'GET /api/charter/availability': 'Check vessel availability', + 'GET /api/charter/bookings': 'List bookings', + 'POST /api/charter/bookings': 'Create booking', + 'GET /api/charter/bookings/:id': 'Get booking details', + 'PUT /api/charter/bookings/:id': 'Update booking', + 'POST /api/charter/bookings/:id/cancel': 'Cancel booking', + 'GET /api/charter/packages': 'List available packages', + 'POST /api/charter/calculate-price': 'Calculate booking price', + + // Maintenance Service + 'GET /api/maintenance/services': 'List available services', + 'GET /api/maintenance/work-orders': 'List work orders', + 'POST /api/maintenance/work-orders': 'Create work order', + 'GET /api/maintenance/work-orders/:id': 'Get work order details', + 'PUT /api/maintenance/work-orders/:id': 'Update work order', + 'GET /api/maintenance/schedule': 'Get maintenance schedule', + 'POST /api/maintenance/schedule': 'Schedule maintenance', + + // Payments + 'POST /api/payments/create-intent': 'Create Stripe payment intent', + 'POST /api/payments/confirm': 'Confirm payment', + 'GET /api/payments/history': 'Get payment history', + 'POST /api/payments/refund': 'Process refund', + + // Invoices + 'GET /api/invoices': 'List invoices', + 'GET /api/invoices/:id': 'Get invoice details', + 'POST /api/invoices/:id/pay': 'Pay invoice', + 'GET /api/invoices/:id/download': 'Download invoice PDF', + + // Documents + 'GET /api/documents': 'List documents', + 'POST /api/documents/upload': 'Upload document', + 'GET /api/documents/:id': 'Get document', + 'DELETE /api/documents/:id': 'Delete document', + + // Notifications + 'POST /api/notifications/email': 'Send email notification', + 'GET /api/notifications/preferences': 'Get notification preferences', + 'PUT /api/notifications/preferences': 'Update preferences', + + // Reports & Analytics + 'GET /api/analytics/dashboard': 'Dashboard metrics', + 'GET /api/analytics/revenue': 'Revenue reports', + 'GET /api/analytics/utilization': 'Vessel utilization' +}; +``` + +--- + +## ๐Ÿณ Docker Configuration + +### Complete docker-compose.yml + +```yaml +version: '3.9' + +networks: + harborsmith: + driver: bridge + +volumes: + postgres_data: + minio_data: + keycloak_data: + directus_uploads: + redis_data: + +services: + # ===== FRONTEND ===== + web: + build: + context: ./apps/web + dockerfile: Dockerfile + args: + - NODE_ENV=production + container_name: harborsmith-web + restart: unless-stopped + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - NEXT_PUBLIC_API_URL=http://api:4000 + - NEXT_PUBLIC_SUPABASE_URL=${SUPABASE_URL} + - NEXT_PUBLIC_SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY} + - NEXT_PUBLIC_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY} + - NEXT_PUBLIC_KEYCLOAK_URL=http://keycloak:8080 + - NEXT_PUBLIC_KEYCLOAK_REALM=harborsmith + - NEXT_PUBLIC_KEYCLOAK_CLIENT_ID=harborsmith-web + depends_on: + - api + - keycloak + networks: + - harborsmith + + # ===== API GATEWAY ===== + api: + build: + context: ./apps/api + dockerfile: Dockerfile + container_name: harborsmith-api + restart: unless-stopped + ports: + - "4000:4000" + environment: + - NODE_ENV=production + - PORT=4000 + - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/harborsmith + - REDIS_URL=redis://redis:6379 + - KEYCLOAK_URL=http://keycloak:8080 + - KEYCLOAK_REALM=harborsmith + - KEYCLOAK_CLIENT_ID=harborsmith-api + - KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET} + - JWT_SECRET=${JWT_SECRET} + - CHARTER_SERVICE_URL=http://charter-service:5001 + - MAINTENANCE_SERVICE_URL=http://maintenance-service:5002 + - PAYMENT_SERVICE_URL=http://payment-service:5003 + - NOTIFICATION_SERVICE_URL=http://notification-service:5004 + depends_on: + - postgres + - redis + - keycloak + networks: + - harborsmith + + # ===== MICROSERVICES ===== + charter-service: + build: + context: ./services/charter + dockerfile: Dockerfile + container_name: harborsmith-charter + restart: unless-stopped + ports: + - "5001:5001" + environment: + - NODE_ENV=production + - PORT=5001 + - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/harborsmith + - REDIS_URL=redis://redis:6379 + depends_on: + - postgres + - redis + networks: + - harborsmith + + maintenance-service: + build: + context: ./services/maintenance + dockerfile: Dockerfile + container_name: harborsmith-maintenance + restart: unless-stopped + ports: + - "5002:5002" + environment: + - NODE_ENV=production + - PORT=5002 + - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/harborsmith + - REDIS_URL=redis://redis:6379 + depends_on: + - postgres + - redis + networks: + - harborsmith + + payment-service: + build: + context: ./services/payments + dockerfile: Dockerfile + container_name: harborsmith-payments + restart: unless-stopped + ports: + - "5003:5003" + environment: + - NODE_ENV=production + - PORT=5003 + - STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY} + - STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET} + - INVOICE_NINJA_URL=${INVOICE_NINJA_URL} + - INVOICE_NINJA_TOKEN=${INVOICE_NINJA_TOKEN} + - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/harborsmith + depends_on: + - postgres + networks: + - harborsmith + + notification-service: + build: + context: ./services/notifications + dockerfile: Dockerfile + container_name: harborsmith-notifications + restart: unless-stopped + ports: + - "5004:5004" + environment: + - NODE_ENV=production + - PORT=5004 + - SMTP_HOST=${POSTE_HOST} + - SMTP_PORT=${POSTE_PORT} + - SMTP_SECURE=${POSTE_SECURE} + - SMTP_USER=${POSTE_USER} + - SMTP_PASS=${POSTE_PASS} + - LISTMONK_URL=${LISTMONK_URL} + - LISTMONK_USER=${LISTMONK_USER} + - LISTMONK_PASS=${LISTMONK_PASS} + networks: + - harborsmith + + # ===== DATABASES & STORAGE ===== + postgres: + image: supabase/postgres:15.1.0.117 + container_name: harborsmith-db + restart: unless-stopped + ports: + - "5432:5432" + environment: + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=harborsmith + volumes: + - postgres_data:/var/lib/postgresql/data + - ./migrations:/docker-entrypoint-initdb.d:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - harborsmith + + redis: + image: redis:7-alpine + container_name: harborsmith-redis + restart: unless-stopped + ports: + - "6379:6379" + volumes: + - redis_data:/data + command: redis-server --appendonly yes + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - harborsmith + + # ===== AUTHENTICATION ===== + keycloak: + image: quay.io/keycloak/keycloak:23.0 + container_name: harborsmith-keycloak + restart: unless-stopped + ports: + - "8080:8080" + environment: + - KEYCLOAK_ADMIN=${KEYCLOAK_ADMIN} + - KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN_PASSWORD} + - KC_DB=postgres + - KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak + - KC_DB_USERNAME=postgres + - KC_DB_PASSWORD=${POSTGRES_PASSWORD} + - KC_HOSTNAME_STRICT=false + - KC_HTTP_ENABLED=true + - KC_HEALTH_ENABLED=true + command: start-dev + volumes: + - keycloak_data:/opt/keycloak/data + depends_on: + - postgres + networks: + - harborsmith + + # ===== FILE STORAGE ===== + minio: + image: minio/minio:latest + container_name: harborsmith-minio + restart: unless-stopped + ports: + - "9000:9000" + - "9001:9001" + environment: + - MINIO_ROOT_USER=${MINIO_ROOT_USER} + - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD} + - MINIO_BROWSER_REDIRECT_URL=http://localhost:9001 + volumes: + - minio_data:/data + command: server /data --console-address ":9001" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 30s + timeout: 20s + retries: 3 + networks: + - harborsmith + + createbuckets: + image: minio/mc:latest + container_name: harborsmith-minio-setup + depends_on: + - minio + entrypoint: > + /bin/sh -c " + /usr/bin/mc config host add harborsmith http://minio:9000 ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD}; + /usr/bin/mc mb harborsmith/documents; + /usr/bin/mc mb harborsmith/images; + /usr/bin/mc mb harborsmith/directus; + /usr/bin/mc policy set public harborsmith/images; + exit 0; + " + networks: + - harborsmith + + # ===== CMS ===== + directus: + image: directus/directus:10.8 + container_name: harborsmith-cms + restart: unless-stopped + ports: + - "8055:8055" + environment: + - KEY=${DIRECTUS_KEY} + - SECRET=${DIRECTUS_SECRET} + - DB_CLIENT=pg + - DB_HOST=postgres + - DB_PORT=5432 + - DB_DATABASE=directus + - DB_USER=postgres + - DB_PASSWORD=${POSTGRES_PASSWORD} + - ADMIN_EMAIL=${DIRECTUS_ADMIN_EMAIL} + - ADMIN_PASSWORD=${DIRECTUS_ADMIN_PASSWORD} + - PUBLIC_URL=http://localhost:8055 + - STORAGE_LOCATIONS=s3 + - STORAGE_S3_DRIVER=s3 + - STORAGE_S3_ENDPOINT=http://minio:9000 + - STORAGE_S3_BUCKET=directus + - STORAGE_S3_REGION=us-east-1 + - STORAGE_S3_KEY=${MINIO_ROOT_USER} + - STORAGE_S3_SECRET=${MINIO_ROOT_PASSWORD} + volumes: + - directus_uploads:/directus/uploads + depends_on: + - postgres + - minio + networks: + - harborsmith + + # ===== ANALYTICS ===== + umami: + image: ghcr.io/umami-software/umami:postgresql-latest + container_name: harborsmith-analytics + restart: unless-stopped + ports: + - "3001:3000" + environment: + - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/umami + - DATABASE_TYPE=postgresql + - HASH_SALT=${UMAMI_HASH_SALT} + - TRACKER_SCRIPT_NAME=script.js + - DISABLE_UPDATES=true + depends_on: + - postgres + networks: + - harborsmith +``` + +--- + +## ๐Ÿ”„ Migration Strategy: Mockups to Production + +### Phase 1: Component Extraction (Days 1-3) + +#### 1. Extract Design System +```javascript +// scripts/migrate-mockups.js +const fs = require('fs'); +const path = require('path'); +const cheerio = require('cheerio'); + +// Extract colors, fonts, and styles from existing CSS +const extractDesignTokens = () => { + const cssFiles = [ + 'website-mockups/css/styles.css', + 'website-mockups/css/themes.css', + 'website-mockups/css/voyage-layout.css' + ]; + + // Parse CSS and generate Tailwind config + // Extract color variables + // Extract font definitions + // Generate component classes +}; + +// Convert HTML components to React +const convertToReact = (htmlFile) => { + const html = fs.readFileSync(htmlFile, 'utf8'); + const $ = cheerio.load(html); + + // Extract components + const navigation = $('.voyage-nav').html(); + const hero = $('.hero-voyage').html(); + const footer = $('.voyage-footer').html(); + + // Generate React components + // Handle event listeners + // Convert class names to Tailwind + // Extract inline styles +}; +``` + +#### 2. Component Mapping +```typescript +// Component extraction map +interface ComponentMap { + source: string; // HTML file location + target: string; // React component location + dependencies: string[]; // Required components + data: string[]; // Required data/props +} + +const componentMappings: ComponentMap[] = [ + { + source: 'website-mockups/index.html', + target: 'apps/web/src/components/home/Hero.tsx', + dependencies: ['Button', 'Container'], + data: ['heroTitle', 'heroSubtitle', 'ctaText'] + }, + { + source: 'website-mockups/charter.html', + target: 'apps/web/src/components/charter/FleetGrid.tsx', + dependencies: ['Card', 'Badge', 'Image'], + data: ['vessels', 'pricing', 'availability'] + }, + // ... more mappings +]; +``` + +### Phase 2: React Component Generation (Days 4-7) + +#### Example: Hero Component +```typescript +// apps/web/src/components/home/Hero.tsx +'use client'; + +import { motion } from 'framer-motion'; +import { ArrowRight, Anchor } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { useTheme } from '@/hooks/useTheme'; +import Link from 'next/link'; + +export const Hero = () => { + const { theme } = useTheme(); + + return ( +
+ {/* Background gradient from mockup */} +
+ + {/* Wave animation from original */} +
+ + + +
+ + {/* Content */} +
+ + + + San Francisco Bay's Premier Yacht Services + + +

+ Your Journey Begins at{' '} + + HarborSmith + +

+ +

+ Experience luxury yacht charters and professional maintenance services + with San Francisco's most trusted maritime partner +

+ +
+ + + +
+
+
+
+ ); +}; +``` + +--- + +## ๐Ÿ“… Implementation Timeline + +### Week 1: Infrastructure & Foundation + +#### Day 1-2: Project Setup +- [ ] Initialize Git repository +- [ ] Set up monorepo with pnpm workspaces +- [ ] Configure Docker environments (dev/prod) +- [ ] Set up environment variables +- [ ] Initialize Next.js with TypeScript +- [ ] Configure ESLint and Prettier + +#### Day 3-4: Database & Auth +- [ ] Deploy PostgreSQL with Supabase +- [ ] Run initial migrations +- [ ] Configure Keycloak realm +- [ ] Set up SSO clients +- [ ] Test authentication flow +- [ ] Configure Row Level Security + +#### Day 5: Services Setup +- [ ] Create API Gateway structure +- [ ] Initialize microservices +- [ ] Set up service discovery +- [ ] Configure Redis caching +- [ ] Set up MinIO buckets +- [ ] Deploy Directus CMS + +### Week 2: Core Application + +#### Day 6-7: Frontend Foundation +- [ ] Convert mockup styles to Tailwind +- [ ] Create component library +- [ ] Implement theme system +- [ ] Set up routing structure +- [ ] Create layout components +- [ ] Implement navigation + +#### Day 8-9: Public Pages +- [ ] Homepage with all sections +- [ ] Charter service pages +- [ ] Maintenance service pages +- [ ] About page +- [ ] Contact page +- [ ] FAQ page + +#### Day 10: Authentication Integration +- [ ] Keycloak provider setup +- [ ] Login/Register flows +- [ ] Protected routes +- [ ] Role-based access +- [ ] Session management +- [ ] Logout functionality + +### Week 3: Portal Development + +#### Day 11-12: User Dashboard +- [ ] Dashboard layout +- [ ] Charter dashboard widgets +- [ ] Maintenance dashboard widgets +- [ ] Activity timeline +- [ ] Quick actions +- [ ] Notifications panel + +#### Day 13-14: Booking Systems +- [ ] Charter booking wizard +- [ ] Maintenance scheduling +- [ ] Calendar integration +- [ ] Availability checking +- [ ] Price calculation +- [ ] Booking confirmation + +#### Day 15: Document Management +- [ ] File upload to MinIO +- [ ] Document viewer +- [ ] Document categorization +- [ ] Download functionality +- [ ] Document sharing +- [ ] Version control + +### Week 4: Integration & Launch + +#### Day 16-17: Payment & Invoicing +- [ ] Stripe integration +- [ ] Payment intents +- [ ] InvoiceNinja API +- [ ] Invoice generation +- [ ] Payment history +- [ ] Refund processing + +#### Day 18-19: Testing & Optimization +- [ ] End-to-end testing +- [ ] Performance optimization +- [ ] Security audit +- [ ] Load testing +- [ ] Bug fixes +- [ ] Mobile optimization + +#### Day 20: Deployment +- [ ] Production configuration +- [ ] SSL certificates +- [ ] DNS configuration +- [ ] Monitoring setup +- [ ] Backup configuration +- [ ] Go-live checklist + +--- + +## ๐Ÿš€ Quick Start Guide + +### Prerequisites +```bash +# Required software +- Node.js 20+ +- Docker & Docker Compose +- pnpm (npm install -g pnpm) +- Git +``` + +### Initial Setup +```bash +# 1. Clone the repository +git clone [repository-url] +cd harborsmith-platform + +# 2. Install dependencies +pnpm install + +# 3. Set up environment variables +cp .env.example .env +# Edit .env with your configuration + +# 4. Start Docker services +docker-compose up -d + +# 5. Run database migrations +docker exec harborsmith-db psql -U postgres -d harborsmith -f /docker-entrypoint-initdb.d/001_initial_schema.sql +docker exec harborsmith-db psql -U postgres -d harborsmith -f /docker-entrypoint-initdb.d/002_create_organizations.sql +# ... run all migrations + +# 6. Seed demo data +pnpm run seed + +# 7. Start development servers +pnpm run dev + +# 8. Convert mockups to React components +pnpm run migrate:mockups +``` + +### Access Points +``` +Frontend: http://localhost:3000 +API Gateway: http://localhost:4000 +Keycloak: http://localhost:8080 +Directus CMS: http://localhost:8055 +MinIO Console: http://localhost:9001 +Umami Analytics: http://localhost:3001 +``` + +### Development Commands +```bash +# Start all services +pnpm run dev + +# Run tests +pnpm run test + +# Build for production +pnpm run build + +# Deploy to production +pnpm run deploy + +# Generate API documentation +pnpm run docs:api + +# Run database migrations +pnpm run db:migrate + +# Seed database +pnpm run db:seed + +# Format code +pnpm run format + +# Lint code +pnpm run lint +``` + +--- + +## ๐ŸŽฏ Critical Path to MVP + +### Must-Have Features (Week 1-2) +1. **User Authentication** + - Login/Register with Keycloak + - Role-based access (Customer, Admin, Service Provider) + - Session management + +2. **Core Booking Flow** + - Browse available services + - Select yacht/service + - Choose date/time + - Complete payment + - Receive confirmation + +3. **Basic Dashboard** + - View bookings + - Manage vessels + - View invoices + - Upload documents + +### Should-Have Features (Week 3) +1. **Communication** + - Email notifications + - Booking confirmations + - Reminder emails + +2. **Payment Processing** + - Stripe integration + - Invoice generation + - Payment history + +3. **Admin Panel** + - Manage users + - View all bookings + - Generate reports + +### Nice-to-Have Features (Week 4+) +1. **Advanced Features** + - Real-time availability + - Dynamic pricing + - Weather integration + - Route planning + +2. **Analytics** + - Revenue tracking + - Utilization reports + - Customer insights + +--- + +## ๐Ÿ”’ Security Considerations + +### Authentication & Authorization +- Keycloak SSO with MFA support +- JWT tokens with refresh mechanism +- Role-based access control (RBAC) +- API key management for services + +### Data Protection +- TLS/SSL for all communications +- Encryption at rest (database) +- Encryption in transit +- PII data isolation +- GDPR compliance measures + +### Infrastructure Security +- Container security scanning +- Regular dependency updates +- Rate limiting on APIs +- DDoS protection +- Regular security audits + +--- + +## ๐Ÿ“Š Performance Targets + +### Frontend Performance +```yaml +Metrics: + First Contentful Paint: < 1.5s + Time to Interactive: < 3.5s + Cumulative Layout Shift: < 0.1 + Largest Contentful Paint: < 2.5s + +Optimization: + - Next.js ISR for static content + - Image optimization with next/image + - Code splitting per route + - Lazy loading components + - CDN for static assets +``` + +### Backend Performance +```yaml +API Response Times: + GET endpoints: < 200ms (p95) + POST endpoints: < 300ms (p95) + Database queries: < 50ms (p95) + +Optimization: + - Redis caching layer + - Database query optimization + - Connection pooling + - Horizontal scaling ready +``` + +--- + +## ๐Ÿšจ Monitoring & Alerts + +### Application Monitoring +- Umami Analytics for user behavior +- Custom dashboards for business metrics +- Error tracking with Sentry (optional) +- Performance monitoring + +### Infrastructure Monitoring +- Container health checks +- Database performance metrics +- API response times +- Resource utilization + +### Alerting Rules +- Downtime > 1 minute +- Error rate > 1% +- Response time > 1s +- Database connection failures +- Payment processing failures + +--- + +## ๐Ÿ“ Deployment Checklist + +### Pre-Deployment +- [ ] All tests passing +- [ ] Security audit complete +- [ ] Performance benchmarks met +- [ ] Documentation updated +- [ ] Environment variables configured +- [ ] SSL certificates ready + +### Deployment Steps +- [ ] Backup existing data +- [ ] Deploy database migrations +- [ ] Deploy backend services +- [ ] Deploy frontend application +- [ ] Verify all services running +- [ ] Run smoke tests + +### Post-Deployment +- [ ] Monitor error rates +- [ ] Check performance metrics +- [ ] Verify email delivery +- [ ] Test payment processing +- [ ] Update DNS records +- [ ] Announce go-live + +--- + +## ๐Ÿค Team Collaboration + +### Git Workflow +```bash +main +โ”œโ”€โ”€ develop +โ”‚ โ”œโ”€โ”€ feature/user-authentication +โ”‚ โ”œโ”€โ”€ feature/booking-system +โ”‚ โ””โ”€โ”€ feature/payment-integration +โ”œโ”€โ”€ staging +โ””โ”€โ”€ production +``` + +### Code Review Process +1. Create feature branch from develop +2. Implement feature with tests +3. Create pull request +4. Automated tests run +5. Code review by team +6. Merge to develop +7. Deploy to staging +8. Test in staging +9. Merge to main +10. Deploy to production + +--- + +## ๐Ÿ“š Additional Resources + +### Documentation +- API Documentation: `/docs/API_DOCUMENTATION.md` +- Deployment Guide: `/docs/DEPLOYMENT_GUIDE.md` +- Contributing Guide: `/docs/CONTRIBUTING.md` +- Security Policy: `/docs/SECURITY.md` + +### External Resources +- [Next.js Documentation](https://nextjs.org/docs) +- [Supabase Documentation](https://supabase.io/docs) +- [Keycloak Documentation](https://www.keycloak.org/documentation) +- [Docker Documentation](https://docs.docker.com) +- [Stripe Documentation](https://stripe.com/docs) + +### Support +- Technical Issues: Create GitHub issue +- Security Issues: security@harborsmith.com +- Business Inquiries: business@harborsmith.com + +--- + +## ๐ŸŽฏ Success Metrics + +### Technical Metrics +- 99.9% uptime +- < 3s page load time +- < 300ms API response time +- Zero critical security vulnerabilities + +### Business Metrics +- 500+ vessels onboarded (Year 1) +- 100+ service providers +- 1000+ monthly active users +- 15% month-over-month growth + +--- + +## โœ… Conclusion + +This implementation plan provides everything needed to build HarborSmith from the ground up: + +1. **Modern Architecture**: Microservices, SSR, real-time capabilities +2. **Enterprise Security**: Keycloak SSO, RLS, encryption +3. **Scalable Infrastructure**: Docker, Redis, horizontal scaling ready +4. **Developer Experience**: TypeScript, hot reloading, automated testing +5. **Production Ready**: Monitoring, analytics, backup strategies + +With this plan, you can start development immediately and have a production-ready MVP within 4 weeks. + +**Next Steps:** +1. Set up development environment +2. Begin with Week 1 infrastructure tasks +3. Convert mockups to React components +4. Implement core booking flows +5. Deploy to staging for testing +6. Launch MVP to production + +--- + +*This document is a living guide and should be updated as the project evolves.* diff --git a/maintenance-documents-screenshot.png b/maintenance-documents-screenshot.png new file mode 100644 index 0000000..abb7e4d Binary files /dev/null and b/maintenance-documents-screenshot.png differ diff --git a/maintenance-invoices-screenshot.png b/maintenance-invoices-screenshot.png new file mode 100644 index 0000000..70e2671 Binary files /dev/null and b/maintenance-invoices-screenshot.png differ diff --git a/maintenance-reports-screenshot.png b/maintenance-reports-screenshot.png new file mode 100644 index 0000000..c5f543e Binary files /dev/null and b/maintenance-reports-screenshot.png differ diff --git a/maintenance-schedule-screenshot.png b/maintenance-schedule-screenshot.png new file mode 100644 index 0000000..93d063a Binary files /dev/null and b/maintenance-schedule-screenshot.png differ diff --git a/nuxt.config.ts b/nuxt.config.ts new file mode 100644 index 0000000..bbcba86 --- /dev/null +++ b/nuxt.config.ts @@ -0,0 +1,159 @@ +import { existsSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' + +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + compatibilityDate: '2024-04-03', + devtools: { enabled: true }, + + // Static Site Generation + ssr: true, + nitro: { + prerender: { + routes: ['/'], + crawlLinks: true + } + }, + + // Modules + modules: [ + '@nuxt/image', + '@nuxtjs/tailwindcss', + '@nuxtjs/google-fonts', + // '@nuxtjs/seo', // Temporarily disabled - incompatible with Nuxt 3.19.2 + '@vueuse/nuxt', + '@vueuse/motion/nuxt' + ], + + // Google Fonts + googleFonts: { + families: { + 'Inter': [300, 400, 500, 600, 700, 800], + 'Playfair+Display': [400, 700, 900] + }, + display: 'swap', + preload: true, + prefetch: false, + preconnect: true + }, + + // SEO - disabled temporarily + // site: { + // url: 'https://harborsmith.com', + // name: 'Harbor Smith', + // description: 'Premium yacht charter and maintenance services in San Francisco Bay', + // defaultLocale: 'en' + // }, + + // ogImage: { + // enabled: false + // }, + + // Image optimization + image: { + quality: 90, + format: ['webp', 'jpg'], + screens: { + xs: 320, + sm: 640, + md: 768, + lg: 1024, + xl: 1280, + xxl: 1536, + '2xl': 1536 + } + }, + + // Tailwind CSS + tailwindcss: { + exposeConfig: true, + viewer: false, + config: { + content: [], + theme: { + extend: { + colors: { + 'harbor-blue': '#001f3f', + 'harbor-navy': '#1e3a5f', + 'harbor-gold': '#b48b4e', + 'harbor-amber': '#9d7943', + 'harbor-yellow': '#c9a56f', + 'harbor-light': '#f0f0f0' + }, + fontFamily: { + sans: ['Inter', 'sans-serif'], + serif: ['Playfair Display', 'serif'] + }, + animation: { + 'fade-in': 'fadeIn 0.6s ease-out', + 'slide-up': 'slideUp 0.6s ease-out', + 'scale-in': 'scaleIn 0.5s ease-out' + }, + keyframes: { + fadeIn: { + '0%': { opacity: '0' }, + '100%': { opacity: '1' } + }, + slideUp: { + '0%': { transform: 'translateY(30px)', opacity: '0' }, + '100%': { transform: 'translateY(0)', opacity: '1' } + }, + scaleIn: { + '0%': { transform: 'scale(0.9)', opacity: '0' }, + '100%': { transform: 'scale(1)', opacity: '1' } + } + }, + backgroundImage: { + 'gradient-warm': 'linear-gradient(135deg, #b48b4e 0%, #c9a56f 100%)', + 'gradient-blue': 'linear-gradient(135deg, #001f3f 0%, #1e3a5f 100%)' + } + } + } + } + }, + + // App configuration + app: { + head: { + title: 'Harbor Smith - Premium Yacht Charter & Maintenance', + meta: [ + { charset: 'utf-8' }, + { name: 'viewport', content: 'width=device-width, initial-scale=1' }, + { name: 'description', content: 'Experience luxury yacht charters and professional maintenance services in San Francisco Bay with Harbor Smith.' }, + { name: 'format-detection', content: 'telephone=no' } + ], + link: [ + { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } + ] + }, + pageTransition: { name: 'page', mode: 'out-in' }, + layoutTransition: { name: 'layout', mode: 'out-in' } + }, + + // CSS + css: [ + '~/assets/css/voyage-layout.css', + '~/assets/css/themes.css', + '~/assets/css/main.css' + ], + + // Runtime config + runtimeConfig: { + public: { + siteUrl: process.env.NUXT_PUBLIC_SITE_URL || 'https://harborsmith.com' + } + }, + + hooks: { + 'prepare:types': () => { + const buildDir = join(process.cwd(), '.nuxt') + const content = JSON.stringify({ extends: './tsconfig.json' }, null, 2) + for (const file of ['tsconfig.app.json', 'tsconfig.shared.json']) { + const target = join(buildDir, file) + if (!existsSync(target)) { + writeFileSync(target, content + '\n', 'utf8') + } + } + } + } +}) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d8e67f8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,75 @@ +{ + "name": "harborsmith", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "harborsmith", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@playwright/test": "^1.55.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz", + "integrity": "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==", + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.55.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz", + "integrity": "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.55.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz", + "integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..36485cc --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "harborsmith", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "@playwright/test": "^1.55.0" + } +} diff --git a/test-maintenance-pages.js b/test-maintenance-pages.js new file mode 100644 index 0000000..dd0fb6c --- /dev/null +++ b/test-maintenance-pages.js @@ -0,0 +1,106 @@ +const { chromium } = require('playwright'); +const path = require('path'); + +async function testMaintenancePages() { + const browser = await chromium.launch({ headless: false }); + const context = await browser.newContext(); + const page = await context.newPage(); + + const baseDir = 'Z:\\Repos\\harborsmith\\website-mockups\\maintenance'; + const pages = [ + 'maintenance-dashboard.html', + 'maintenance-schedule.html', + 'maintenance-documents.html', + 'maintenance-reports.html', + 'maintenance-invoices.html' + ]; + + console.log('Starting maintenance portal testing...\n'); + + for (const pageName of pages) { + const filePath = `file:///${baseDir}/${pageName}`; + console.log(`Testing: ${pageName}`); + + try { + await page.goto(filePath, { waitUntil: 'networkidle' }); + + // Take screenshot + const screenshotPath = `Z:\\Repos\\harborsmith\\screenshot-${pageName.replace('.html', '')}.png`; + await page.screenshot({ path: screenshotPath, fullPage: true }); + console.log(`Screenshot saved: ${screenshotPath}`); + + // Get page title and basic info + const title = await page.title(); + console.log(`Page title: ${title}`); + + // Check for any console errors + page.on('console', msg => { + if (msg.type() === 'error') { + console.log(`Console error: ${msg.text()}`); + } + }); + + // Test interactive elements based on page + if (pageName === 'maintenance-dashboard.html') { + // Try to click on navigation items or cards + const navItems = await page.locator('nav a, .card, .btn').count(); + console.log(`Found ${navItems} interactive elements on dashboard`); + } + + if (pageName === 'maintenance-schedule.html') { + // Try to interact with schedule form + const formElements = await page.locator('input, select, button').count(); + console.log(`Found ${formElements} form elements on schedule page`); + } + + if (pageName === 'maintenance-reports.html') { + // Try to expand a report + const reports = await page.locator('.report-item, .accordion, .expandable').count(); + console.log(`Found ${reports} report items`); + + // Try to click first expandable element if it exists + const firstExpandable = page.locator('.report-item, .accordion-header, [data-toggle]').first(); + if (await firstExpandable.count() > 0) { + try { + await firstExpandable.click(); + await page.waitForTimeout(1000); + const expandedScreenshot = `Z:\\Repos\\harborsmith\\screenshot-${pageName.replace('.html', '')}-expanded.png`; + await page.screenshot({ path: expandedScreenshot, fullPage: true }); + console.log(`Expanded view screenshot: ${expandedScreenshot}`); + } catch (e) { + console.log('Could not interact with expandable element:', e.message); + } + } + } + + if (pageName === 'maintenance-invoices.html') { + // Try to show payment modal + const payButtons = await page.locator('button:has-text("Pay"), .pay-btn, [data-payment]').count(); + console.log(`Found ${payButtons} payment buttons`); + + if (payButtons > 0) { + try { + await page.locator('button:has-text("Pay"), .pay-btn, [data-payment]').first().click(); + await page.waitForTimeout(1000); + const modalScreenshot = `Z:\\Repos\\harborsmith\\screenshot-${pageName.replace('.html', '')}-modal.png`; + await page.screenshot({ path: modalScreenshot, fullPage: true }); + console.log(`Modal view screenshot: ${modalScreenshot}`); + } catch (e) { + console.log('Could not interact with payment button:', e.message); + } + } + } + + await page.waitForTimeout(2000); + console.log(`โœ“ ${pageName} tested successfully\n`); + + } catch (error) { + console.log(`โœ— Error testing ${pageName}: ${error.message}\n`); + } + } + + await browser.close(); + console.log('Testing complete!'); +} + +testMaintenancePages().catch(console.error); \ No newline at end of file diff --git a/website-mockups/Anodes.jpg b/website-mockups/Anodes.jpg new file mode 100644 index 0000000..48b39d3 Binary files /dev/null and b/website-mockups/Anodes.jpg differ diff --git a/website-mockups/CLIENT-PORTAL-IMPLEMENTATION-GUIDE.md b/website-mockups/CLIENT-PORTAL-IMPLEMENTATION-GUIDE.md new file mode 100644 index 0000000..a7f9f3b --- /dev/null +++ b/website-mockups/CLIENT-PORTAL-IMPLEMENTATION-GUIDE.md @@ -0,0 +1,327 @@ +# HarborSmith Client Portal - Implementation Guide + +## Completed Components + +### โœ… Core Infrastructure +1. **portal-styles.css** - Complete CSS framework for the portal + - Dashboard layouts and cards + - Form styles and validation states + - Tables and data displays + - Status badges and indicators + - Responsive design breakpoints + - Theme support (4 themes) + +2. **portal-login.html** - Unified login page + - Role selection (Maintenance/Charter) + - OAuth integration placeholders + - Demo credentials system + - Video background with brand messaging + +3. **maintenance-dashboard.html** - Main maintenance dashboard + - Fleet overview with health scores + - Upcoming services calendar + - Invoice status tracking + - Recent activity timeline + - Quick action buttons + +4. **maintenance-vessels.html** - Vessel management page + - Grid view of registered vessels + - Add new vessel modal + - Health score visualization + - Quick schedule buttons + +## Remaining Pages to Implement + +### Maintenance Portal Pages + +#### 1. **maintenance-schedule.html** +**Features:** +- Interactive calendar view (monthly/weekly/daily) +- Service type selector dropdown +- Available time slots display +- Vessel selection with current status +- Service urgency levels (Emergency/Urgent/Routine) +- Preferred technician selection +- Special instructions text area +- Confirmation and reminder settings + +#### 2. **maintenance-documents.html** +**Features:** +- Document categories sidebar + - Registration documents + - Insurance policies + - Service warranties + - Safety certificates + - Owner manuals +- Drag-and-drop upload zone +- Document preview modal +- Expiry date tracking with alerts +- Search and filter functionality +- Bulk download option +- Document sharing controls + +#### 3. **maintenance-reports.html** +**Features:** +- Filterable report list (by date, vessel, service type) +- Detailed report viewer with sections: + - Service summary + - Parts replaced + - Labor performed + - Before/after photos + - Technician recommendations + - Next service suggestions +- Comment/note addition +- PDF export functionality +- Service rating and feedback + +#### 4. **maintenance-invoices.html** +**Features:** +- Invoice list with status filters (Paid/Pending/Overdue) +- Detailed invoice view with line items +- Payment method selection + - Credit card form + - ACH transfer details + - Wire instructions +- Payment history table +- Auto-pay configuration +- Dispute/question form +- Download receipts + +### Charter Portal Pages + +#### 5. **charter-dashboard.html** +**Features:** +- Current charter countdown timer +- Weather widget for charter date +- Itinerary preview card +- Guest list summary +- Payment status indicator +- Recommended add-ons carousel +- Recent messages from crew +- Pre-charter checklist + +#### 6. **charter-packages.html** +**Features:** +- Current package details card +- Available upgrades grid: + - Gourmet catering tiers + - Water sports equipment + - Photography/videography + - Special occasion decorations + - Extended hours + - Additional crew +- Package comparison table +- Pricing calculator with real-time updates +- Save package as template + +#### 7. **charter-itinerary.html** +**Features:** +- Interactive map with waypoints +- Draggable timeline editor +- Activity cards: + - Departure/arrival times + - Anchor locations + - Meal times + - Activity slots + - Sunset viewing spots +- Weather overlay on timeline +- Share itinerary via email/link +- Print-friendly version +- Captain's notes section + +#### 8. **charter-payments.html** +**Features:** +- Payment summary breakdown: + - Base charter fee + - Add-ons and upgrades + - Additional guests + - Taxes and fees + - Gratuity calculator +- Guest addition form ($150/guest) +- Payment schedule display +- Saved payment methods +- Transaction history +- Refund policy accordion +- Split payment option + +#### 9. **charter-documents.html** +**Features:** +- Required documents checklist: + - Photo IDs for all guests + - Signed waivers + - Insurance proof + - Medical/dietary forms + - Emergency contacts +- Upload progress tracker +- Document status badges +- Secure preview modal +- Download all as ZIP +- Missing document alerts + +#### 10. **charter-profile.html** +**Features:** +- Personal information form +- Emergency contacts (primary/secondary) +- Preferences section: + - Dietary restrictions + - Allergies + - Music preferences + - Beverage selections + - Activity interests +- Previous charters gallery +- Favorite vessels +- Saved itineraries +- Communication preferences +- Photo memories timeline + +## Implementation Details + +### Navigation Structure +All pages share consistent sidebar navigation with: +- Collapsible sidebar toggle +- Active state highlighting +- Role-based menu items +- User profile dropdown +- Notification badge + +### Common Components +- **Header**: Breadcrumb navigation, search, notifications, user menu +- **Cards**: Consistent shadow, hover effects, border radius +- **Forms**: Validation states, required field indicators, help text +- **Modals**: Overlay, close button, responsive sizing +- **Tables**: Sortable headers, hover states, responsive scroll + +### Interactive Features +1. **Form Validation** + - Real-time field validation + - Error message display + - Success confirmation toasts + +2. **Data Tables** + - Column sorting + - Pagination + - Search/filter + - Export options + +3. **File Uploads** + - Drag-and-drop zones + - Progress indicators + - File type validation + - Preview generation + +4. **Calendar Integration** + - Date picker widgets + - Time slot selection + - Availability checking + - Reminder settings + +### Mock Data Structure +```javascript +// Sample user data +const maintenanceUser = { + name: "John Smith", + email: "maintenance@demo.com", + role: "maintenance", + vessels: [ + { + id: 1, + name: "Sea Breeze", + type: "Sailboat", + model: "Beneteau Oceanis 45", + year: 2019, + healthScore: 95, + lastService: "2024-11-10", + nextService: "2024-12-15" + } + ], + invoices: [ + { + id: "INV-1247", + amount: 2850, + status: "paid", + date: "2024-11-15" + } + ] +}; + +const charterUser = { + name: "Sarah Johnson", + email: "charter@demo.com", + role: "charter", + currentBooking: { + vessel: "Bay Dreamer", + date: "2024-12-20", + duration: "4 hours", + guests: 8, + package: "Premium Sunset" + } +}; +``` + +### Responsive Breakpoints +- **Mobile**: < 768px (stacked layout, hidden sidebar) +- **Tablet**: 768px - 1024px (condensed sidebar, 2-column grids) +- **Desktop**: > 1024px (full sidebar, multi-column layouts) + +### Security Considerations +- Session management with localStorage +- Secure document handling +- Input sanitization +- HTTPS enforcement (production) +- Two-factor authentication ready + +### Performance Optimizations +- Lazy loading for images +- Pagination for large datasets +- Debounced search inputs +- Skeleton loaders for async content +- Minified CSS/JS (production) + +## Next Steps for Full Implementation + +1. **Backend Integration** + - API endpoints for CRUD operations + - Authentication system + - File upload handling + - Payment processing + - Email notifications + +2. **Database Schema** + - Users table with roles + - Vessels table with specifications + - Services table with scheduling + - Documents table with metadata + - Invoices table with line items + +3. **Third-Party Integrations** + - Stripe/PayPal for payments + - SendGrid for emails + - AWS S3 for document storage + - Google Calendar API + - Weather API + +4. **Testing Requirements** + - Unit tests for form validation + - Integration tests for workflows + - Accessibility testing (WCAG 2.1) + - Cross-browser compatibility + - Performance benchmarks + +5. **Deployment Considerations** + - SSL certificates + - CDN for static assets + - Database backups + - Monitoring and logging + - Support ticket system + +## Design Consistency Notes + +All pages maintain: +- Warm, inviting color palette from main site +- Inter + Playfair Display typography +- Consistent spacing using CSS variables +- Smooth transitions and hover effects +- Professional yet approachable tone +- Mobile-first responsive design + +This comprehensive portal system provides a complete solution for both maintenance and charter clients while maintaining the HarborSmith brand identity and user experience standards. \ No newline at end of file diff --git a/website-mockups/Calendar.jpg b/website-mockups/Calendar.jpg new file mode 100644 index 0000000..bc22e3e Binary files /dev/null and b/website-mockups/Calendar.jpg differ diff --git a/website-mockups/DESIGN-SYSTEM.md b/website-mockups/DESIGN-SYSTEM.md new file mode 100644 index 0000000..cd616f7 --- /dev/null +++ b/website-mockups/DESIGN-SYSTEM.md @@ -0,0 +1,814 @@ +# HarborSmith Design System +## Voyage Theme Standard + +### Version 1.0 +Last Updated: December 2024 + +--- + +## ๐ŸŽจ Core Design Philosophy + +The HarborSmith Voyage theme embodies the spirit of maritime adventure with warm, inviting colors that evoke feelings of sunset voyages and golden horizons. Our design system combines nautical heritage with modern luxury, creating an experience that is both timeless and contemporary. + +### Design Principles +1. **Warmth & Welcome** - Every element should feel inviting and aspirational +2. **Maritime Heritage** - Subtle nautical references without being clichรฉ +3. **Premium Experience** - Convey luxury through spacing, typography, and imagery +4. **Emotional Connection** - Use warm gradients and cinematic visuals to inspire adventure +5. **Accessibility** - Maintain WCAG AA compliance across all color combinations + +--- + +## ๐ŸŽจ Color Palette + +### Primary Colors + +#### Classic Nautical (Default Theme) +```css +--primary-blue: #001f3f; /* Deep Navy - Headers, Primary Text */ +--warm-orange: #dc143c; /* Crimson Red - CTAs, Accents */ +--warm-amber: #b91c3c; /* Deep Crimson - Hover States */ +--warm-yellow: #ef4444; /* Coral Red - Highlights */ +--soft-cream: #f0f4f8; /* Light Blue-Gray - Backgrounds */ +--text-dark: #0a1628; /* Near Black - Body Text */ +--text-light: #4a5568; /* Gray - Secondary Text */ +--white: #ffffff; /* Pure White - Cards, Contrast */ +``` + +### Gradients +```css +--gradient-warm: linear-gradient(135deg, #dc143c 0%, #ef4444 100%); +--gradient-sunset: linear-gradient(135deg, #b91c3c 0%, #dc143c 50%, #ef4444 100%); +--gradient-ocean: linear-gradient(135deg, #001f3f 0%, #003366 100%); +``` + +### Semantic Colors +```css +--success: #10B981; /* Emerald Green */ +--warning: #F59E0B; /* Amber */ +--error: #EF4444; /* Red */ +--info: #3B82F6; /* Blue */ +``` + +### Usage Guidelines +- **Primary Blue (#001f3f)**: Main headers, navigation (scrolled state), footer backgrounds +- **Warm Orange (#dc143c)**: Primary CTA buttons, active states, important accents +- **Warm Amber (#b91c3c)**: Hover states for primary actions +- **Soft Cream (#f0f4f8)**: Section backgrounds, alternate row colors +- **Gradients**: Hero sections, premium features, CTA buttons + +--- + +## ๐Ÿ“ Typography + +### Font Families +```css +--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; +--font-display: 'Playfair Display', serif; +``` + +### Type Scale +```css +/* Display - Hero Headlines */ +.display-1 { + font-size: clamp(3rem, 8vw, 5rem); + font-family: var(--font-display); + font-weight: 900; + line-height: 1.1; +} + +/* Headings */ +h1 { + font-size: clamp(2.5rem, 5vw, 3.5rem); + font-family: var(--font-display); + font-weight: 700; + line-height: 1.2; +} + +h2 { + font-size: clamp(2rem, 4vw, 2.5rem); + font-family: var(--font-display); + font-weight: 700; + line-height: 1.3; +} + +h3 { + font-size: clamp(1.5rem, 3vw, 1.875rem); + font-family: var(--font-sans); + font-weight: 600; + line-height: 1.4; +} + +h4 { + font-size: 1.25rem; + font-family: var(--font-sans); + font-weight: 600; + line-height: 1.5; +} + +/* Body Text */ +.body-large { + font-size: 1.125rem; + line-height: 1.7; +} + +.body-regular { + font-size: 1rem; + line-height: 1.6; +} + +.body-small { + font-size: 0.875rem; + line-height: 1.5; +} +``` + +### Typography Usage +- **Playfair Display**: Hero headlines, section titles, brand name +- **Inter**: Body text, navigation, buttons, forms +- **Font Weights**: 300 (light), 400 (regular), 500 (medium), 600 (semibold), 700 (bold), 800 (extrabold), 900 (black) + +--- + +## ๐Ÿ“ Spacing System + +### Base Unit: 8px +```css +--space-xs: 0.5rem; /* 8px */ +--space-sm: 1rem; /* 16px */ +--space-md: 2rem; /* 32px */ +--space-lg: 3rem; /* 48px */ +--space-xl: 4rem; /* 64px */ +--space-2xl: 6rem; /* 96px */ +--space-3xl: 8rem; /* 128px */ +``` + +### Container Widths +```css +--container-sm: 640px; +--container-md: 768px; +--container-lg: 1024px; +--container-xl: 1200px; +--container-2xl: 1400px; +``` + +### Section Padding +- Mobile: `padding: var(--space-lg) var(--space-md);` +- Tablet: `padding: var(--space-xl) var(--space-md);` +- Desktop: `padding: var(--space-2xl) var(--space-md);` + +--- + +## ๐ŸŽฏ Components + +### Buttons + +#### Primary Button (Warm Gradient) +```css +.btn-primary { + background: var(--gradient-warm); + color: white; + padding: 0.875rem 2rem; + border-radius: 50px; + font-weight: 600; + font-size: 1rem; + border: none; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + display: inline-flex; + align-items: center; + gap: 0.5rem; +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 10px 30px rgba(220, 20, 60, 0.3); +} +``` + +#### Secondary Button (Ghost) +```css +.btn-secondary { + background: transparent; + color: var(--warm-orange); + padding: 0.875rem 2rem; + border: 2px solid var(--warm-orange); + border-radius: 50px; + font-weight: 600; + transition: all 0.3s ease; +} + +.btn-secondary:hover { + background: var(--warm-orange); + color: white; + transform: translateY(-2px); +} +``` + +### Cards + +#### Standard Card +```css +.card { + background: white; + border-radius: 16px; + padding: var(--space-md); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + transition: all 0.3s ease; +} + +.card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); +} +``` + +#### Premium Card (with gradient border) +```css +.card-premium { + background: white; + border-radius: 20px; + padding: 3px; + background: var(--gradient-warm); +} + +.card-premium-inner { + background: white; + border-radius: 17px; + padding: var(--space-md); +} +``` + +### Navigation + +#### Fixed Navigation Bar +```css +.navigation { + position: fixed; + top: 0; + width: 100%; + z-index: 1000; + transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Transparent state (hero) */ +.navigation { + background: rgba(255, 255, 255, 0); + backdrop-filter: blur(0); +} + +/* Scrolled state */ +.navigation.scrolled { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); +} +``` + +### Forms + +#### Input Fields +```css +.form-input { + width: 100%; + padding: 0.875rem 1.25rem; + border: 2px solid #E5E7EB; + border-radius: 12px; + font-size: 1rem; + transition: all 0.3s ease; +} + +.form-input:focus { + outline: none; + border-color: var(--warm-orange); + box-shadow: 0 0 0 3px rgba(220, 20, 60, 0.1); +} +``` + +#### Dropdown Menus +```jsx +// React Component with Framer Motion +// Standard dropdown pattern for consistent interactions across the platform + +import { motion, AnimatePresence } from 'framer-motion'; +import { ChevronDown } from 'lucide-react'; +import { useState } from 'react'; + +const DropdownMenu = ({ + label = "Select Option", + options = [], + value, + onChange, + placeholder = "Choose...", + icon = null +}) => { + const [isOpen, setIsOpen] = useState(false); + + return ( +
+ + + + {isOpen && ( + + {options.map((option) => ( + { + onChange(option); + setIsOpen(false); + }} + whileHover={{ x: 4 }} + transition={{ type: "spring", stiffness: 300 }} + > + {option.icon && ( + {option.icon} + )} + {option.label} + {option.badge && ( + {option.badge} + )} + + ))} + + )} + +
+ ); +}; +``` + +##### Dropdown Styles (CSS) +```css +.dropdown-container { + position: relative; + width: 100%; +} + +.dropdown-trigger { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; + padding: 0.875rem 1.25rem; + background: white; + border: 2px solid rgba(220, 20, 60, 0.1); + border-radius: 12px; + font-size: 1rem; + font-weight: 500; + color: #0a1628; + cursor: pointer; + transition: all 0.3s ease; +} + +.dropdown-trigger:hover { + border-color: rgba(220, 20, 60, 0.2); + box-shadow: 0 4px 12px rgba(0, 31, 63, 0.08); +} + +.dropdown-trigger:focus { + outline: none; + border-color: #dc143c; + box-shadow: 0 0 0 3px rgba(220, 20, 60, 0.1); +} + +.dropdown-icon { + display: flex; + align-items: center; + color: #dc143c; +} + +.dropdown-value { + flex: 1; + text-align: left; +} + +.dropdown-chevron { + color: #64748b; + transition: transform 0.3s ease; +} + +.dropdown-chevron.rotate-180 { + transform: rotate(180deg); +} + +.dropdown-menu { + position: absolute; + top: calc(100% + 8px); + left: 0; + right: 0; + max-height: 320px; + overflow-y: auto; + background: white; + border: 1px solid rgba(220, 20, 60, 0.08); + border-radius: 12px; + box-shadow: 0 10px 40px rgba(0, 31, 63, 0.12); + z-index: 1000; +} + +.dropdown-option { + width: 100%; + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.875rem 1.25rem; + background: transparent; + border: none; + font-size: 0.95rem; + color: #4a5568; + cursor: pointer; + transition: all 0.2s ease; + text-align: left; +} + +.dropdown-option:hover { + background: linear-gradient(90deg, rgba(220, 20, 60, 0.05), rgba(239, 68, 68, 0.05)); + color: #0a1628; +} + +.dropdown-option:focus { + outline: none; + background: rgba(220, 20, 60, 0.08); +} + +.option-icon { + display: flex; + align-items: center; + color: #dc143c; + width: 20px; +} + +.option-label { + flex: 1; +} + +.option-badge { + padding: 0.25rem 0.5rem; + background: linear-gradient(135deg, #dc143c, #ef4444); + color: white; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 600; +} + +/* Dark Mode Variant */ +.dark .dropdown-trigger { + background: rgba(255, 255, 255, 0.05); + border-color: rgba(255, 255, 255, 0.1); + color: white; +} + +.dark .dropdown-menu { + background: #1a1a2e; + border-color: rgba(255, 255, 255, 0.1); +} + +.dark .dropdown-option { + color: rgba(255, 255, 255, 0.8); +} + +.dark .dropdown-option:hover { + background: rgba(220, 20, 60, 0.2); + color: white; +} +``` + +##### Usage Examples +```jsx +// Basic Dropdown + setSelectedYacht(option.value)} +/> + +// Dropdown with Icons and Badges +} + options={[ + { + value: 'golden-gate', + label: 'Golden Gate Tour', + icon: , + badge: 'Popular' + }, + { + value: 'alcatraz', + label: 'Alcatraz Circle', + icon: + }, + { + value: 'sunset', + label: 'Sunset Cruise', + icon: , + badge: 'Romantic' + } + ]} + value={selectedRoute} + onChange={(option) => setSelectedRoute(option.value)} +/> +``` + +##### Accessibility Guidelines +- Always include proper ARIA attributes (`aria-expanded`, `aria-haspopup`) +- Ensure keyboard navigation works (Tab, Enter, Escape keys) +- Maintain focus management when opening/closing +- Provide clear visual focus indicators +- Support screen readers with semantic HTML + +--- + +## ๐ŸŽฌ Animations + +### Standard Transitions +```css +--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +--transition-slow: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); +--transition-fast: all 0.15s cubic-bezier(0.4, 0, 0.2, 1); +``` + +### Animation Classes +```css +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fade-up { + animation: fadeInUp 0.8s ease forwards; +} + +.animate-fade-up-delay { + animation: fadeInUp 0.8s ease 0.2s forwards; + opacity: 0; +} + +.animate-fade-up-delay-2 { + animation: fadeInUp 0.8s ease 0.4s forwards; + opacity: 0; +} +``` + +### Hover Effects +- **Lift**: `transform: translateY(-4px);` +- **Scale**: `transform: scale(1.05);` +- **Glow**: `box-shadow: 0 10px 30px rgba(220, 20, 60, 0.3);` + +--- + +## ๐Ÿ–ผ๏ธ Imagery Guidelines + +### Hero Images +- **Aspect Ratio**: 16:9 for desktop, 4:3 for mobile +- **Overlay**: Dark gradient overlay (40-60% opacity) for text legibility +- **Quality**: Minimum 1920x1080 for desktop, optimized for web + +### Content Images +- **Border Radius**: 16px for standard images, 20px for featured +- **Shadow**: `box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);` +- **Hover Effect**: Scale 1.05 with transition + +### Image Overlays +```css +.image-overlay { + background: linear-gradient( + to bottom, + rgba(0, 31, 63, 0.3) 0%, + rgba(0, 31, 63, 0.7) 100% + ); +} +``` + +--- + +## ๐Ÿ“ฑ Responsive Breakpoints + +```css +/* Mobile First Approach */ +--breakpoint-sm: 640px; /* Small tablets */ +--breakpoint-md: 768px; /* Tablets */ +--breakpoint-lg: 1024px; /* Small laptops */ +--breakpoint-xl: 1280px; /* Desktop */ +--breakpoint-2xl: 1536px; /* Large screens */ +``` + +### Media Query Usage +```css +/* Mobile (default) */ +.element { } + +/* Tablet and up */ +@media (min-width: 768px) { + .element { } +} + +/* Desktop and up */ +@media (min-width: 1024px) { + .element { } +} +``` + +--- + +## ๐ŸŒŸ Special Effects + +### Gradient Text +```css +.gradient-text { + background: var(--gradient-warm); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} +``` + +### Glass Morphism +```css +.glass { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 16px; +} +``` + +### Warm Glow Effect +```css +.warm-glow { + box-shadow: + 0 0 20px rgba(220, 20, 60, 0.1), + 0 0 40px rgba(220, 20, 60, 0.05); +} +``` + +--- + +## ๐ŸŽฏ Icon System + +### Lucide Icons Configuration +```css +[data-lucide] { + width: 24px; + height: 24px; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; + fill: none; +} +``` + +### Icon Sizes +- **Small**: 16px (inline text) +- **Default**: 24px (buttons, navigation) +- **Medium**: 32px (feature icons) +- **Large**: 48px (hero icons) + +--- + +## โœ… Accessibility Standards + +### Color Contrast +- **Normal Text**: Minimum 4.5:1 contrast ratio +- **Large Text**: Minimum 3:1 contrast ratio +- **Interactive Elements**: Minimum 3:1 contrast ratio + +### Focus States +```css +:focus-visible { + outline: 3px solid var(--warm-orange); + outline-offset: 2px; + border-radius: 4px; +} +``` + +### Touch Targets +- Minimum size: 44x44px +- Spacing between targets: minimum 8px + +--- + +## ๐Ÿš€ Implementation Checklist + +### For Each Page +- [ ] Use correct color palette variables +- [ ] Apply consistent typography scale +- [ ] Maintain spacing system +- [ ] Include smooth transitions +- [ ] Implement hover states +- [ ] Ensure mobile responsiveness +- [ ] Add proper focus states +- [ ] Use semantic HTML +- [ ] Include meta descriptions +- [ ] Optimize images + +### Component Consistency +- [ ] Buttons match design system +- [ ] Forms use standard inputs +- [ ] Cards follow shadow/radius standards +- [ ] Navigation transitions properly +- [ ] Footer matches other pages +- [ ] Icons use Lucide consistently + +--- + +## ๐Ÿ“‹ Quick Reference + +### CSS Variables to Include +```css + + + + +``` + +### Standard Page Structure +```html + + + + + + + + + + + + +
...
+ + +
+
+ +
+
+ + +
...
+ + + + + +``` + +--- + +## ๐Ÿ”„ Version History + +### Version 1.0 (December 2024) +- Initial design system documentation +- Voyage theme standardization +- Color palette definition +- Typography scale +- Component library +- Animation guidelines + +--- + +## ๐Ÿ“ Notes + +This design system is optimized for: +- Modern browsers (Chrome, Firefox, Safari, Edge) +- Mobile-first responsive design +- WCAG AA accessibility standards +- Performance (lazy loading, optimized assets) +- SEO best practices + +For questions or updates, contact the HarborSmith development team. \ No newline at end of file diff --git a/website-mockups/Design-Notes.md b/website-mockups/Design-Notes.md new file mode 100644 index 0000000..e2322fa --- /dev/null +++ b/website-mockups/Design-Notes.md @@ -0,0 +1,2 @@ +## Client Portal ## +User experience: Details, Number of Guests, Packages, Special Occassions, Dog Friendly or not, etc. --> Boat selection, and day and time slots, pickup time and place --> Checkout \ No newline at end of file diff --git a/website-mockups/ExtCleaning.jpg b/website-mockups/ExtCleaning.jpg new file mode 100644 index 0000000..446faed Binary files /dev/null and b/website-mockups/ExtCleaning.jpg differ diff --git a/website-mockups/Foredeck.jpg b/website-mockups/Foredeck.jpg new file mode 100644 index 0000000..b4b4c9c Binary files /dev/null and b/website-mockups/Foredeck.jpg differ diff --git a/website-mockups/HARBOR-SMITH-white.png b/website-mockups/HARBOR-SMITH-white.png new file mode 100644 index 0000000..60c9730 Binary files /dev/null and b/website-mockups/HARBOR-SMITH-white.png differ diff --git a/website-mockups/HARBOR-SMITH_navy.png b/website-mockups/HARBOR-SMITH_navy.png new file mode 100644 index 0000000..2fc22be Binary files /dev/null and b/website-mockups/HARBOR-SMITH_navy.png differ diff --git a/website-mockups/HS-Homepage.pdf b/website-mockups/HS-Homepage.pdf new file mode 100644 index 0000000..d9bc202 Binary files /dev/null and b/website-mockups/HS-Homepage.pdf differ diff --git a/website-mockups/HarborSmith-Platform-Definition.md b/website-mockups/HarborSmith-Platform-Definition.md new file mode 100644 index 0000000..dedfa3b --- /dev/null +++ b/website-mockups/HarborSmith-Platform-Definition.md @@ -0,0 +1,272 @@ +# HarborSmith Platform Definition Document + +## Executive Summary + +HarborSmith is a comprehensive marine services platform that bridges the gap between yacht owners and maritime service providers. The platform operates through two primary service verticals: **Maintenance Services** and **Charter Services**, unified through an integrated client portal system designed to evolve into dedicated mobile and web applications. + +## Company Vision + +To become the premier digital platform for luxury yacht services, providing seamless connections between vessel owners, charter clients, and marine service providers while ensuring exceptional service quality, transparency, and convenience. + +## Platform Structure + +### Core Service Verticals + +#### 1. Maintenance Services Division +**Target Audience:** Yacht owners, vessel managers, and marine facilities + +**Key Services:** +- Scheduled preventive maintenance +- Emergency repair coordination +- Parts procurement and inventory management +- Service provider vetting and management +- Maintenance history tracking +- Compliance and documentation management +- Cost estimation and budget planning +- Real-time service status updates + +**Value Proposition:** +- Centralized maintenance management +- Vetted and certified service providers +- Transparent pricing and scheduling +- Digital maintenance logs and records +- Predictive maintenance recommendations +- 24/7 emergency support coordination + +#### 2. Charter Services Division +**Target Audience:** Charter clients, yacht owners offering vessels for charter, charter brokers + +**Key Services:** +- Vessel availability and booking management +- Charter agreement facilitation +- Crew coordination and management +- Itinerary planning and customization +- Provisioning and catering coordination +- Real-time communication with crew +- Payment processing and invoicing +- Charter history and preferences tracking + +**Value Proposition:** +- Curated selection of luxury vessels +- Transparent pricing and availability +- Personalized charter experiences +- Simplified booking and payment process +- Comprehensive trip planning tools +- Post-charter feedback and ratings + +## Client Portal Architecture + +### Current Phase: Unified Web Portal + +#### Portal Features +**For Maintenance Clients:** +- Dashboard with vessel overview +- Service request submission +- Work order tracking +- Invoice and payment management +- Document repository +- Service history timeline +- Communication center +- Vendor ratings and reviews + +**For Charter Clients:** +- Vessel search and filtering +- Availability calendar +- Booking management +- Trip planning tools +- Communication with crew/captain +- Payment and contract management +- Personal preferences profile +- Charter history and favorites + +**For Service Providers:** +- Job assignment dashboard +- Work order management +- Time and materials tracking +- Invoice generation +- Client communication tools +- Certification management +- Performance metrics +- Payment tracking + +### Future Phase: Dedicated Applications + +#### Mobile Applications (iOS & Android) +**Maintenance App Features:** +- Push notifications for service reminders +- Photo/video upload for issue reporting +- GPS-based emergency service locator +- Offline mode for vessel documentation +- QR code scanning for parts/equipment +- Digital signature for work approvals + +**Charter App Features:** +- Interactive vessel tours (AR/VR capable) +- Real-time vessel tracking during charter +- In-app messaging with crew +- Digital concierge services +- Weather and navigation updates +- Social sharing capabilities + +#### Enhanced Web Applications +- Advanced analytics and reporting +- Multi-vessel fleet management +- Integration with marine industry APIs +- AI-powered maintenance predictions +- Virtual consultation capabilities +- Blockchain-based documentation + +## Technical Infrastructure + +### Platform Requirements +- Cloud-based architecture (AWS/Azure) +- Microservices design pattern +- RESTful API architecture +- Real-time data synchronization +- Multi-tenant database structure +- Enterprise-grade security +- GDPR/privacy compliance +- Scalable infrastructure + +### Integration Capabilities +- Marina management systems +- Marine parts suppliers +- Payment gateways +- Shipping and logistics providers +- Weather and navigation services +- Insurance providers +- Regulatory compliance databases +- Accounting software + +## User Journey Flows + +### Maintenance Service Flow +1. **Registration/Onboarding** + - Vessel registration and documentation + - Service preferences setup + - Preferred vendor selection + +2. **Service Request** + - Issue identification and reporting + - Automated vendor matching + - Quote review and approval + +3. **Service Execution** + - Real-time status tracking + - Direct communication with providers + - Progress photo/video updates + +4. **Completion** + - Digital work order sign-off + - Invoice review and payment + - Service rating and feedback + +### Charter Service Flow +1. **Discovery** + - Browse available vessels + - Filter by preferences and requirements + - View detailed vessel information + +2. **Booking** + - Check availability + - Customize itinerary + - Review and sign charter agreement + +3. **Pre-Charter** + - Provisioning preferences + - Crew communication + - Final preparations + +4. **During Charter** + - Real-time support + - Itinerary adjustments + - Concierge services + +5. **Post-Charter** + - Feedback and ratings + - Photo sharing + - Rebooking options + +## Revenue Model + +### Maintenance Services +- Transaction fees on service bookings (10-15%) +- Premium subscription tiers for vessel owners +- Featured listing fees for service providers +- Data analytics and insights packages +- Emergency service coordination fees + +### Charter Services +- Charter booking commissions (15-20%) +- Premium listing fees for yacht owners +- Concierge service fees +- Travel insurance partnerships +- Provisioning markup + +## Success Metrics + +### Key Performance Indicators (KPIs) +- Monthly Active Users (MAU) +- Service completion rate +- Average transaction value +- Customer satisfaction score (NPS) +- Provider response time +- Platform uptime +- Revenue per user +- User retention rate + +### Growth Targets +- Year 1: 500+ vessels, 100+ service providers +- Year 2: 2,000+ vessels, 500+ service providers +- Year 3: 5,000+ vessels, 1,500+ service providers +- Geographic expansion: 5 major yachting hubs by Year 3 + +## Competitive Advantages + +1. **Unified Platform**: Single solution for both maintenance and charter needs +2. **Verified Network**: Pre-vetted service providers and vessels +3. **Transparency**: Clear pricing, real-time updates, and comprehensive documentation +4. **Technology-First**: Modern UI/UX, mobile-first design, AI-powered features +5. **Industry Expertise**: Deep understanding of maritime operations and regulations +6. **Scalability**: Platform designed to grow from regional to global operations + +## Implementation Roadmap + +### Phase 1: Foundation (Months 1-6) +- Core platform development +- Basic client portal launch +- Initial service provider onboarding +- Beta testing with select clients + +### Phase 2: Expansion (Months 7-12) +- Full portal feature deployment +- Payment system integration +- Marketing campaign launch +- Geographic expansion to 2 regions + +### Phase 3: Mobile Development (Months 13-18) +- iOS application development and launch +- Android application development and launch +- API expansion for third-party integrations +- Advanced analytics implementation + +### Phase 4: Scale and Optimize (Months 19-24) +- AI/ML feature integration +- International expansion +- Enterprise features for fleet management +- Strategic partnership development + +## Risk Management + +### Identified Risks and Mitigation Strategies +- **Technology Risk**: Maintain redundant systems and regular backups +- **Market Risk**: Continuous market research and agile development +- **Regulatory Risk**: Legal compliance team and regular audits +- **Competition Risk**: Focus on unique value propositions and user experience +- **Operational Risk**: Robust vetting process for service providers + +## Conclusion + +HarborSmith represents a transformative approach to yacht services management, combining cutting-edge technology with deep industry expertise. By focusing on both maintenance and charter services through an integrated platform, HarborSmith is positioned to become the definitive solution for yacht owners and charter clients worldwide. + +The evolution from a unified web portal to dedicated applications ensures that the platform can meet users where they are, providing the right tools and features for each user segment while maintaining a cohesive ecosystem that benefits all stakeholders in the luxury yachting industry. \ No newline at end of file diff --git a/website-mockups/Helm.jpg b/website-mockups/Helm.jpg new file mode 100644 index 0000000..b579f16 Binary files /dev/null and b/website-mockups/Helm.jpg differ diff --git a/website-mockups/Hull Clean Pricing.jpg b/website-mockups/Hull Clean Pricing.jpg new file mode 100644 index 0000000..253ad3b Binary files /dev/null and b/website-mockups/Hull Clean Pricing.jpg differ diff --git a/website-mockups/Interior.jpg b/website-mockups/Interior.jpg new file mode 100644 index 0000000..e4bd98a Binary files /dev/null and b/website-mockups/Interior.jpg differ diff --git a/website-mockups/Licensed.jpg b/website-mockups/Licensed.jpg new file mode 100644 index 0000000..be4c9e1 Binary files /dev/null and b/website-mockups/Licensed.jpg differ diff --git a/website-mockups/PROJECT_STATUS.md b/website-mockups/PROJECT_STATUS.md new file mode 100644 index 0000000..2f2b5a2 --- /dev/null +++ b/website-mockups/PROJECT_STATUS.md @@ -0,0 +1,212 @@ +# HarborSmith Website Mockups - Project Status + +## ๐ŸŽฏ Project Overview +A modern, responsive website for HarborSmith Yacht Services featuring: +- Custom yacht chartering services +- Yacht maintenance services +- Theme switcher with 4 color schemes +- Fully connected click-through demo +- Google Inter font throughout +- Modern animations and interactions + +## โœ… Completed Components + +### 1. **Homepage (index.html)** โœ“ +- Hero section with gradient background and wave animation +- Dual service cards (Charter & Maintenance) +- Fleet showcase with 3 yachts (Bay Dreamer, Golden Gate Spirit, Pacific Explorer) +- Animated statistics section +- Testimonial slider (3 testimonials) +- Call-to-action section +- Full footer with contact info and links + +### 2. **Navigation System** โœ“ +- Sticky header with scroll effects +- Dropdown mega menu for services +- Mobile responsive hamburger menu +- Theme switcher with 4 color schemes +- Active page highlighting + +### 3. **Theme System** โœ“ +Four complete color themes: +- **Classic Nautical** (Navy blue + Red) +- **Sunset Bay** (Dark blue + Yellow) +- **Ocean Breeze** (Teal + Coral) +- **Harbor Lights** (Slate + Amber) + +### 4. **Charter Service Page (charter.html)** โœ“ +- Service hero section +- Detailed fleet information (3 yachts) +- Pricing packages (Half Day, Full Day, Sunset Special) +- Popular routes section (4 routes) +- Individual yacht details with features + +### 5. **Charter Booking Flow - Step 1 (charter-booking-1.html)** โœ“ +- 4-step progress indicator +- Calendar date picker +- Time selection dropdown +- Duration options (2, 3, 4, 8 hours) +- Guest number input +- Booking summary sidebar +- Help section with phone contact + +### 6. **CSS Architecture** โœ“ +- **styles.css** - Main styles, components, layout +- **themes.css** - Color scheme definitions +- **animations.css** - Keyframes and animation utilities +- **booking.css** - Booking flow specific styles + +### 7. **JavaScript Functionality** โœ“ +- **main.js** - Theme switcher, navigation, testimonials, counters +- **animations.js** - Scroll animations, parallax, page transitions +- **booking.js** - Calendar, form validation, selection handlers + +## ๐Ÿšง Pages That Need to Be Created + +### Charter Booking Flow (Remaining) +1. **charter-booking-2.html** - Yacht Selection + - Display 3 yacht options with availability + - Selection interface + - Price calculation based on duration + +2. **charter-booking-3.html** - Customize Experience + - Add-on services (catering, water sports, etc.) + - Special requests + - Route selection + +3. **charter-booking-4.html** - Contact & Confirmation + - Contact form + - Payment options + - Terms acceptance + - Final summary + +### Maintenance Section +4. **maintenance.html** - Maintenance Services Main + - Service categories + - Pricing structure + - Service area coverage + - Booking CTA + +5. **maintenance-booking-1.html** - Service Selection + - Service type checkboxes + - Problem description + +6. **maintenance-booking-2.html** - Boat Details + - Boat information form + - Location details + - Photo upload + +7. **maintenance-booking-3.html** - Schedule & Confirm + - Calendar for scheduling + - Contact information + - Quote request + +### Additional Pages +8. **about.html** - About Page + - Company history + - Team/crew profiles + - Certifications + - Marina location + +9. **faq.html** - FAQ Page + - Tabbed sections (Charter/Maintenance) + - Searchable accordion + - Common questions + +10. **contact.html** - Contact Page + - Dual contact forms + - Location map + - Business hours + - Emergency contacts + +## ๐Ÿ”— Current Link Structure + +### Working Links: +- Homepage โ†’ Charter Service Page โœ“ +- Homepage โ†’ Charter Booking Step 1 โœ“ +- Charter Page โ†’ Charter Booking Step 1 โœ“ +- Navigation between Home and Charter โœ“ + +### Broken Links (pages not yet created): +- Homepage โ†’ Maintenance Services +- Homepage โ†’ About +- Homepage โ†’ FAQ +- Homepage โ†’ Contact +- Charter Booking Step 1 โ†’ Step 2 +- All maintenance-related links +- Footer links to non-existent pages + +## ๐ŸŽจ Design Features Implemented + +### Animations: +- Fade-in animations on scroll +- Counter animations for statistics +- Testimonial auto-rotation +- Button hover effects with transforms +- Wave animation in hero +- Floating yacht icons + +### Interactive Elements: +- Theme switcher with localStorage persistence +- Calendar date picker +- Duration selection cards +- Dropdown menus +- Mobile navigation toggle + +### Responsive Design: +- Mobile: 320px - 768px โœ“ +- Tablet: 769px - 1024px โœ“ +- Desktop: 1025px - 1440px โœ“ +- Wide: 1441px+ โœ“ + +## ๐Ÿ“‹ To Complete the Click-Through Demo + +### Priority 1 (Essential for Demo): +1. Create remaining charter booking pages (2, 3, 4) +2. Create maintenance.html main page +3. Create at least one maintenance booking page +4. Create contact.html + +### Priority 2 (Nice to Have): +5. Complete all maintenance booking flow +6. Create about.html +7. Create faq.html + +### Priority 3 (Polish): +8. Add form submission handling +9. Add loading states +10. Add success/error messages +11. Add 404 page + +## ๐Ÿ› Known Issues +1. Some links point to non-existent pages +2. Form submissions don't actually submit anywhere +3. Calendar functionality is visual only +4. Mobile menu might overlap on very small screens + +## ๐Ÿ’ก Recommendations for Completion +1. Focus on completing the charter booking flow first (most important user journey) +2. Create a simplified maintenance page with basic booking +3. Add contact page for credibility +4. Test all navigation paths +5. Ensure theme switcher works on all pages + +## ๐Ÿ“Š Project Statistics +- **Total Files Created**: 10 +- **HTML Pages**: 3 of ~14 needed +- **CSS Files**: 4 (complete) +- **JS Files**: 3 (complete) +- **Color Themes**: 4 (complete) +- **Responsive Breakpoints**: 4 (complete) + +## ๐Ÿš€ Next Steps +1. Complete charter booking flow (3 pages) +2. Create maintenance service page +3. Create contact page +4. Test complete user journey +5. Fix any broken links + +--- + +*Last Updated: Current Session* +*Status: ~30% Complete for full demo* \ No newline at end of file diff --git a/website-mockups/QRCode-White-sm.png b/website-mockups/QRCode-White-sm.png new file mode 100644 index 0000000..fa5dcd8 Binary files /dev/null and b/website-mockups/QRCode-White-sm.png differ diff --git a/website-mockups/QRCode-White.png b/website-mockups/QRCode-White.png new file mode 100644 index 0000000..a1d2f4d Binary files /dev/null and b/website-mockups/QRCode-White.png differ diff --git a/website-mockups/QRCode.png b/website-mockups/QRCode.png new file mode 100644 index 0000000..e4dd403 Binary files /dev/null and b/website-mockups/QRCode.png differ diff --git a/website-mockups/SpecialRequest.jpg b/website-mockups/SpecialRequest.jpg new file mode 100644 index 0000000..b05c750 Binary files /dev/null and b/website-mockups/SpecialRequest.jpg differ diff --git a/website-mockups/Washdown.jpg b/website-mockups/Washdown.jpg new file mode 100644 index 0000000..f0cebcb Binary files /dev/null and b/website-mockups/Washdown.jpg differ diff --git a/website-mockups/Washdown2.jpg b/website-mockups/Washdown2.jpg new file mode 100644 index 0000000..87fc2df Binary files /dev/null and b/website-mockups/Washdown2.jpg differ diff --git a/website-mockups/Waxing.jpg b/website-mockups/Waxing.jpg new file mode 100644 index 0000000..c3a881c Binary files /dev/null and b/website-mockups/Waxing.jpg differ diff --git a/website-mockups/about.html b/website-mockups/about.html new file mode 100644 index 0000000..ee36df7 --- /dev/null +++ b/website-mockups/about.html @@ -0,0 +1,914 @@ + + + + + + About HarborSmith - Our Story + + + + + + + + + + + + + +
+
+

Our Story

+

Three generations of maritime excellence, serving the San Francisco Bay since 2009

+
+
+ + +
+
+
+
+

Est. 2009

+

Born from Passion

+

+ HarborSmith began as a dream shared by Captain James Harbor and master technician Sarah Smith. + Both maritime enthusiasts with decades of combined experience, they saw an opportunity to create + something special in San Francisco Bay. +

+

+ What started as a single boat and a small workshop has grown into the Bay Area's most trusted + name in yacht services. Our commitment to excellence, safety, and unforgettable experiences has + remained unchanged since day one. +

+

+ Today, we're proud to be a family-owned business that treats every customer like family, + every yacht like our own, and every voyage like an adventure worth remembering. +

+
+
+ Our journey +
+ Our flagship yacht passing under the Golden Gate Bridge +
+
+
+ +
+
+

The HarborSmith Difference

+

More Than a Service

+

+ We believe that yachting is about more than just boats โ€“ it's about freedom, adventure, and + creating memories that last a lifetime. That's why we go beyond simply providing services. +

+

+ Our charter captains are storytellers who know every hidden cove and spectacular view. Our + maintenance technicians are craftsmen who treat every vessel with the care it deserves. And our + entire team shares a genuine passion for helping you experience the magic of life on the water. +

+

+ Whether you're celebrating a special occasion, maintaining your prized vessel, or simply escaping + the everyday, we're here to make it extraordinary. +

+
+
+ Happy customers +
+ Creating unforgettable moments for our clients +
+
+
+
+
+ + +
+
+
+

Our Core Values

+

The principles that guide everything we do

+
+ +
+
+
+ +
+

Safety First

+

+ Your safety is our top priority. Every vessel is meticulously maintained, every crew member + is fully certified, and every journey follows the highest safety standards. +

+
+ +
+
+ +
+

Excellence

+

+ We don't just meet expectations โ€“ we exceed them. From our premium fleet to our expert + technicians, we deliver nothing but the best. +

+
+ +
+
+ +
+

Passion

+

+ We love what we do, and it shows. Our enthusiasm for maritime life is contagious, making + every interaction a pleasure. +

+
+ +
+
+ +
+

Family

+

+ As a family business, we treat our customers, employees, and community like family. Your + trust is our most valuable asset. +

+
+
+
+
+ + +
+
+
+

Meet Our Crew

+

The experienced professionals behind your perfect day on the water

+
+ +
+
+
JH
+
+

Captain James Harbor

+

Founder & Head Captain

+

+ With over 30 years of sailing experience and a Coast Guard Master's license, James has + navigated waters around the world before making San Francisco Bay his home. +

+
+ USCG Licensed + 30+ Years Experience + CPR Certified +
+
+
+ +
+
SS
+
+

Sarah Smith

+

Co-Founder & Service Director

+

+ A master technician with certifications from all major marine manufacturers, Sarah ensures + every yacht in our care receives the best possible service. +

+
+ ABYC Certified + Mercury Master Tech + Volvo Specialist +
+
+
+ +
+
MH
+
+

Michael Harbor

+

Charter Captain

+

+ Following in his father's footsteps, Michael brings youthful energy and extensive local + knowledge to every charter, ensuring guests have an unforgettable experience. +

+
+ USCG Licensed + 10+ Years Experience + Dive Master +
+
+
+ +
+
RT
+
+

Roberto Torres

+

Lead Technician

+

+ Roberto's expertise in marine electronics and engine systems makes him invaluable. His + attention to detail ensures every yacht runs perfectly. +

+
+ NMEA Certified + Yamaha Specialist + 15+ Years Experience +
+
+
+
+
+
+ + +
+
+
+

Our Journey

+

Milestones that shaped who we are today

+
+ +
+
+
+
2009
+
HarborSmith Founded
+
+ James and Sarah start HarborSmith with one boat and a dream +
+
+
+
+ +
+
+
2012
+
Fleet Expansion
+
+ Added two more yachts and opened our maintenance facility +
+
+
+
+ +
+
+
2015
+
100th Charter
+
+ Celebrated our 100th successful charter with a community event +
+
+
+
+ +
+
+
2018
+
Award Recognition
+
+ Named "Best Yacht Services" by Bay Area Boating Magazine +
+
+
+
+ +
+
+
2020
+
Community Support
+
+ Provided free maintenance to first responders during pandemic +
+
+
+
+ +
+
+
2024
+
15 Years Strong
+
+ Celebrating 15 years of service with our biggest fleet yet +
+
+
+
+
+
+
+ + +
+
+
+

Certified Excellence

+

Recognized by leading maritime organizations

+
+ +
+
+ +
US Coast Guard
+
+
+ +
American Boat & Yacht Council
+
+
+ +
National Marine Electronics
+
+
+ +
Better Business Bureau
+
+
+
+
+ + +
+
+
+
+

Find Us at Pier 39

+

+ Located at the heart of San Francisco's waterfront, our marina offers easy access, ample parking, + and stunning views. We're just minutes from downtown and perfectly positioned for Bay adventures. +

+
+
+ + Pier 39, Dock J, San Francisco, CA 94133 +
+
+ + Open 7 days a week, 8:00 AM - 8:00 PM +
+
+ + Free parking for charter guests +
+
+ + BART and Muni accessible +
+
+
+
+ +
+
+
+
+ + +
+
+
+

Ready to Experience the HarborSmith Difference?

+

+ Join our family of satisfied customers and discover why we're the Bay Area's premier yacht service +

+ +
+
+ + + +

Book a Charter

+

Start your adventure today

+ +
+ + + +
+ + + +

Schedule Service

+

Keep your yacht pristine

+ +
+
+
+
+
+ + +
+ +
+ + + + \ No newline at end of file diff --git a/website-mockups/charter-booking-1.html b/website-mockups/charter-booking-1.html new file mode 100644 index 0000000..59e2703 --- /dev/null +++ b/website-mockups/charter-booking-1.html @@ -0,0 +1,1262 @@ + + + + + + Book Charter - Step 1 - HarborSmith + + + + + + + + + + + + + + + +
+
+
+

Plan Your Perfect Charter

+

Select your preferred date and customize your luxury yacht experience

+
+
+
+ + +
+
+
+
+
+
+
1
+
Date & Time
+
+
+
2
+
Select Yacht
+
+
+
3
+
Customize
+
+
+
4
+
Confirm
+
+
+
+ + +
+ +
+ +
+
+
+ + + + + + +
+
+

Select Charter Date

+

Choose your preferred sailing date

+
+
+ +
+
+
December 2024
+
+ + +
+
+ +
+
Sun
+
Mon
+
Tue
+
Wed
+
Thu
+
Fri
+
Sat
+ + +
29
+
30
+
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
+
+ + +
+
+ + + + +

Available Time Slots

+
+
+
9:00 AM
+
10:00 AM
+
11:00 AM
+
12:00 PM
+
1:00 PM
+
2:00 PM
+
3:00 PM
+
4:00 PM
+
+
+ + +
+
+
+ + + +
+
+

Charter Duration

+

Select your preferred charter length

+
+
+ +
+
+
+
+
Half Day
+
4 hours of luxury
+
+
$2,500
+
+
Perfect for sunset cruises and celebrations
+
+ +
+
+
+
Overnight
+
24 hours escape
+
+
$8,500
+
+
Sleep under the stars, wake on the water
+
+
+
+
+
Weekend
+
48 hours voyage
+
+
$15,000
+
+
Ultimate luxury weekend getaway
+
+
+
+ + +
+
+
+ + + + + + +
+
+

Number of Guests

+

How many will be joining you?

+
+
+ +
+
Guest Count
+
+ +
8
+ +
+
+
+ + + + + + Maximum capacity varies by yacht (8-20 guests) +
+
+
+
+ + +
+
+

Booking Summary

+ +
+
+ + + + + + + Date +
+
December 14, 2024
+
+ +
+
+ + + + + Time +
+
10:00 AM
+
+ +
+
+ + + + Duration +
+
Full Day (8 hours)
+
+ +
+
+ + + + + Guests +
+
8 People
+
+ +
+ +
+
+
Base Charter
+
$4,500
+
+
+
Guest Fee (8 ร— $150)
+
$1,200
+
+
+
+
Total
+
$5,700
+
+
+ + + +
+
Need Assistance?
+
Our charter specialists are available 24/7
+ + + + + Call (415) 555-0123 + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/website-mockups/charter-booking-2.html b/website-mockups/charter-booking-2.html new file mode 100644 index 0000000..97f1fc3 --- /dev/null +++ b/website-mockups/charter-booking-2.html @@ -0,0 +1,1435 @@ + + + + + + Select Your Yacht - HarborSmith + + + + + + + + + + + + + + + +
+
+
+

Choose Your Perfect Yacht

+

Discover our exclusive collection of luxury yachts, each offering a unique experience on the San Francisco Bay

+
+
+
+ + +
+
+
+
+
+
+
โœ“
+
Date & Time
+
+
+
2
+
Select Yacht
+
+
+
3
+
Customize
+
+
+
4
+
Confirm
+
+
+
+ + +
+ +
+
+ Type: + + + + +
+ +
+ Capacity: + + + +
+ +
+ Compare +
+
+
+
+
+ + +
+

Yacht Comparison

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeaturesAzure DreamOcean MajestyWind Dancer
TypeMotor YachtSuper YachtSailing Yacht
Capacity12 guests20 guests8 guests
Length85 feet120 feet65 feet
Crew8 members15 members4 members
AmenitiesWiFi, Jet Ski, ChefHelicopter Pad, Spa, PoolWiFi, Kitchen, Snorkeling
Price (4 hours)$6,000$14,000$3,400
+
+ + +
+ +
+ +
+ Azure Dream +
+
+ Available +
+
+
+
+
+

Azure Dream

+ Motor Yacht +
+
+ + + + 4.9 +
+
+ +
+
+
+ + + + + + +
+
+ 12 + Guests +
+
+
+
+ + + + + + +
+
+ 85ft + Length +
+
+
+
+ + + + + +
+
+ 8 + Crew +
+
+
+ +
+
Premium Features
+
+
+ + + + + WiFi +
+
+ + + + + Jet Ski +
+
+ + + + + + + + Chef Service +
+
+ + + + + Water Sports +
+
+
+ +
+
+ 4 hours + $6,000 +
+ +
+
+
+ + +
+ +
+ Ocean Majesty +
+
+ Limited +
+
+
+
+
+

Ocean Majesty

+ Super Yacht +
+
+ + + + 5.0 +
+
+ +
+
+
+ + + + + + +
+
+ 20 + Guests +
+
+
+
+ + + + + + +
+
+ 120ft + Length +
+
+
+
+ + + + + +
+
+ 15 + Crew +
+
+
+ +
+
Ultimate Luxury
+
+
+ + + + + Helicopter Pad +
+
+ + + + + Michelin Chef +
+
+ + + + + Spa & Pool +
+
+
+ +
+
+ 4 hours + $14,000 +
+ +
+
+
+ + +
+
+ Wind Dancer +
+
+ Available +
+
+
+
+
+

Wind Dancer

+ Sailing Yacht +
+
+ + + + 4.7 +
+
+ +
+
+
+ + + + +
+
+ 8 + Guests +
+
+
+
+ + + + + + +
+
+ 65ft + Length +
+
+
+
+ + + + + +
+
+ 4 + Crew +
+
+
+ +
+
Classic Features
+
+
+ + + + + WiFi +
+
+ + + + Galley Kitchen +
+
+ + + + + Snorkeling Gear +
+
+
+ +
+
+ 4 hours + $3,400 +
+ +
+
+
+
+ + + +
+ + + + + + + + + \ No newline at end of file diff --git a/website-mockups/charter-booking-3.html b/website-mockups/charter-booking-3.html new file mode 100644 index 0000000..d2adf94 --- /dev/null +++ b/website-mockups/charter-booking-3.html @@ -0,0 +1,1555 @@ + + + + + + Customize Your Experience - HarborSmith + + + + + + + + + + + + + + +
+
+

Customize Your Experience

+

Create your perfect luxury yacht charter with premium add-ons and services

+ + +
+
+
+
1
+
Date & Time
+
+
+
2
+
Select Yacht
+
+
+
3
+
Customize
+
+
+
4
+
Confirm
+
+
+
+
+
+
+
+
+ + +
+
+
+ +
+ +
+
+
+ +
+

Choose Your Route

+
+

+ Select your preferred sailing route for the perfect 4-hour adventure +

+
+
+
+ Golden Gate + Popular +
+
Golden Gate Tour
+
Classic Bay experience with iconic bridge views
+
+
+
+ Alcatraz +
+
Alcatraz Circle
+
Historic waters around the infamous island
+
+
+
+ Sunset + Romantic +
+
Sunset Cruise
+
Romantic evening with stunning sunset views
+
+
+
+ Custom +
+
Custom Route
+
Design your own unique adventure
+
+
+
+ + +
+
+
+ +
+

Catering & Refreshments

+
+

+ Elevate your experience with gourmet catering options +

+
+
+
+ No Catering +
+ +
+
โœ“
+
+
+
No Catering
+
Bring your own food and drinks
+
+ BYOB + Ice provided +
+
Included
+
+
+
+
+ Light Refreshments +
+ +
+
โœ“
+
+
+
Light Refreshments
+
Snacks, soft drinks, and water
+
+ Artisan snacks + Premium beverages +
+
+$250
+
+
+
+
+ Premium Package +
+ +
+
โœ“
+
+
+
Premium Package
+
Gourmet appetizers, wine & champagne
+
+ Chef-prepared + Wine selection + Champagne +
+
+$650
+
+
+
+
+ Full Dining +
+ +
+
โœ“
+
+
+
Full Dining Experience
+
3-course meal with premium bar
+
+ Private chef + Full bar + Desserts +
+
+$1,200
+
+
+
+
+ + +
+
+
+ +
+

Premium Add-ons

+
+

+ Enhance your charter with these exclusive services +

+
+
+
+ +
+
+
+ Professional Photographer +
+
+ Capture your memories with a professional photographer for 2 hours +
+
+
+$450
+
+
+
+
+ +
+
+
+ Special Decorations +
+
+ Custom decorations for birthdays, anniversaries, or proposals +
+
+
+$200
+
+
+
+
+ +
+
+
+ Water Sports Package +
+
+ Kayaks, paddleboards, and snorkeling equipment +
+
+
+$350
+
+
+
+
+ +
+
+
+ Fishing Equipment +
+
+ Professional fishing gear and bait included +
+
+
+$250
+
+
+
+
+ +
+
+
+ Live Music/DJ +
+
+ Premium sound system with DJ service or live musician +
+
+
+$150
+
+
+
+
+ +
+
+
+ Hotel Transfer +
+
+ Round-trip luxury transportation from your hotel +
+
+
+$300
+
+
+
+
+ + +
+
+
+ +
+

Special Occasion?

+
+

+ Let us know if you're celebrating something special +

+
+ + + + + + + + +
+
+ + +
+
+
+ +
+

Special Requests

+
+

+ Any special requests or things we should know about? +

+ +
+ + +
+ + +
+
+ + + +
+
+
+ + +
+ +
+ + + + \ No newline at end of file diff --git a/website-mockups/charter-booking-4.html b/website-mockups/charter-booking-4.html new file mode 100644 index 0000000..a8b7419 --- /dev/null +++ b/website-mockups/charter-booking-4.html @@ -0,0 +1,1424 @@ + + + + + + Complete Your Booking - HarborSmith + + + + + + + + + + + + + + +
+
+
+

Complete Your Charter Booking

+

You're almost ready to set sail on your luxury adventure

+ + +
+
+
+
โœ“
+
Date & Time
+
+
+
โœ“
+
Select Yacht
+
+
+
โœ“
+
Customize
+
+
+
4
+
Confirm
+
+
+
+
+
+
+
+
+ + +
+
+
+ +
+
+

+ + Complete Your Booking +

+

+ You're almost ready to set sail! Please provide your contact details and payment information. +

+ + +
+
+
+ +
+

Contact Information

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ +
+

Emergency Contact

+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ +
+

Payment Method

+
+ +
+
+
๐Ÿ’ณ
+
Credit/Debit Card
+
+
+
๐Ÿ’ฐ
+
PayPal
+
+
+
๐Ÿฆ
+
Wire Transfer
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + + + + +
+
+
+ + + + + + +
+ + +
+
+ + + + + 256-bit SSL Encryption +
+
+ + + + PCI DSS Compliant +
+
+ + + + Secure Checkout +
+
+ + +
+

+ + Terms & Conditions +

+ + + + + + +
+ + +
+ + +
+
+ + + +
+ + +
+ +
+ +
+ + +
+ +
+ + + + \ No newline at end of file diff --git a/website-mockups/charter.html b/website-mockups/charter.html new file mode 100644 index 0000000..b53bacf --- /dev/null +++ b/website-mockups/charter.html @@ -0,0 +1,1358 @@ + + + + + + Luxury Yacht Charters - HarborSmith + + + + + + + + + + + + + +
+ Luxury Yacht +
+
+

+ Luxury Yacht + Charter Experience +

+

Discover the ultimate maritime luxury in San Francisco Bay

+
+ + +
+
+
+ +
+
+ + +
+
+
+

Our Premium Fleet

+

Choose from our collection of luxury yachts, each offering unique experiences and world-class amenities

+
+ +
+ +
+
+ Bay Dreamer +
+ + Premium +
+
+ +
+
+
+ + + + + + (5.0) +
+
+ + San Francisco Bay +
+
+
+
+
+
+
Bay Dreamer
+
+ + + 12 guests + + + + 85ft + +
+
+
+
$2,500
+
per day
+
+
+
+ Jacuzzi + Chef + Water Sports +
+
+ + +
+
+
+ + +
+
+ Azure Dream +
+ + Premium +
+
+ +
+
+
+ + + + + + (5.0) +
+
+ + San Francisco Bay +
+
+
+
+
+
+
Azure Dream
+
+ + + 8 guests + + + + 65ft + +
+
+
+
$1,800
+
per day
+
+
+
+ Sunset Deck + Snorkeling + Bar +
+
+ + +
+
+
+ + +
+
+ Royal Serenity +
+ + Premium +
+
+ +
+
+
+ + + + + + (5.0) +
+
+ + San Francisco Bay +
+
+
+
+
+
+
Royal Serenity
+
+ + + 16 guests + + + + 95ft + +
+
+
+
$3,200
+
per day
+
+
+
+ Helicopter Pad + Spa + Cinema +
+
+ + +
+
+
+
+
+
+ + +
+
+
+

Charter Packages

+

Flexible packages designed to create unforgettable maritime experiences

+
+ +
+ +
+
Half Day
+
$1,200
+
4 hours
+
    +
  • + โœ“ + Professional crew +
  • +
  • + โœ“ + Welcome drinks +
  • +
  • + โœ“ + Snorkeling equipment +
  • +
  • + โœ“ + Light refreshments +
  • +
  • + โœ“ + Fuel included +
  • +
+ +
+ + + + + +
+
Multi-Day
+
$8,500
+
3 days
+
    +
  • + โœ“ + Professional crew +
  • +
  • + โœ“ + All meals included +
  • +
  • + โœ“ + Premium accommodations +
  • +
  • + โœ“ + Island hopping +
  • +
  • + โœ“ + All activities +
  • +
  • + โœ“ + Concierge service +
  • +
+ +
+
+
+
+ + +
+
+
+

Popular Routes

+

Explore breathtaking destinations across San Francisco Bay

+
+ +
+ San Francisco Bay Route +
+
+ + Golden Gate Bridge +
+
+ + Alcatraz Island +
+
+ + Angel Island +
+
+ + Sausalito +
+
+
+

Bay Area Paradise

+

Golden Gate โ€ข Alcatraz โ€ข Angel Island โ€ข Sausalito

+ +
+
+
+
+ + +
+
+
+

What Our Guests Say

+

Hear from those who have experienced the ultimate in luxury yacht charter

+
+ + +
+
+ + +
+
+
+

Ready to Set Sail?

+

Book your luxury yacht charter today and create memories that will last a lifetime

+
+ + +
+
+
+
+ + +
+ +
+ + + + \ No newline at end of file diff --git a/website-mockups/contact.html b/website-mockups/contact.html new file mode 100644 index 0000000..b215d88 --- /dev/null +++ b/website-mockups/contact.html @@ -0,0 +1,1012 @@ + + + + + + Contact Us - HarborSmith + + + + + + + + + + + + + +
+

Get in Touch

+

We're here to help you plan your perfect day on the water or keep your yacht in pristine condition

+
+ + +
+
+
+
+
+ +
+

Call Us

+

Speak directly with our friendly team for immediate assistance

+ (415) 555-0123 +
+ +
+
+ +
+

Email Us

+

Send us your questions and we'll respond within 24 hours

+ hello@harborsmith.com +
+ +
+
+ +
+

Visit Us

+

Stop by our marina to see our fleet and meet the team

+ Pier 39, San Francisco +
+ +
+
+ +
+

Emergency Service

+

24/7 emergency repairs and on-water assistance

+ (415) 555-0911 +
+
+
+
+ + +
+
+
+ +
+
+

Send Us a Message

+

Fill out the form below and we'll get back to you as soon as possible

+
+ + +
+ + + +
+ + +
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ * Required fields + +
+
+ + + + + + +
+ + +
+ +
+

Contact Information

+ +
+ +
+
Main Line
+
(415) 555-0123
+
+
+ +
+ +
+
24/7 Emergency
+
(415) 555-0911
+
+
+ +
+ +
+
General Inquiries
+
hello@harborsmith.com
+
+
+ +
+ +
+
Charter Bookings
+
charter@harborsmith.com
+
+
+ +
+ +
+
Service Department
+
service@harborsmith.com
+
+
+
+ + +
+

Business Hours

+ +
+
+ Monday - Friday + 8:00 AM - 8:00 PM +
+
+ Saturday + 8:00 AM - 8:00 PM +
+
+ Sunday + 8:00 AM - 8:00 PM +
+
+ Holidays + 9:00 AM - 6:00 PM +
+
+ +

+ * Emergency service available 24/7 +

+
+ + +
+

Response Times

+

+ Phone: Immediate during business hours
+ Email: Within 24 hours
+ Emergency: Available 24/7
+ Quote Requests: Within 48 hours +

+
+
+
+
+
+ + +
+
+
+

Find Us at Pier 39

+

Conveniently located at San Francisco's iconic waterfront

+
+ +
+
+

HarborSmith Marina

+

Address:
Pier 39, Dock J
San Francisco, CA 94133

+

Parking:
Free 3-hour parking for customers

+

Public Transit:
F-Line Streetcar
BART + Muni accessible

+ +
+ +
+
+
+ + + + + +
+ +
+ + + + \ No newline at end of file diff --git a/website-mockups/css-backup-20250918-135142/animations.css b/website-mockups/css-backup-20250918-135142/animations.css new file mode 100644 index 0000000..702c2b2 --- /dev/null +++ b/website-mockups/css-backup-20250918-135142/animations.css @@ -0,0 +1,435 @@ +/* HarborSmith - Animation Styles */ +/* ========================= */ + +/* Fade Animations */ +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInDown { + from { + opacity: 0; + transform: translateY(-30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInLeft { + from { + opacity: 0; + transform: translateX(-30px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes fadeInRight { + from { + opacity: 0; + transform: translateX(30px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +/* Scale Animations */ +@keyframes scaleIn { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes pulse { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } +} + +/* Rotate Animations */ +@keyframes rotate { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +/* Slide Animations */ +@keyframes slideInLeft { + from { + transform: translateX(-100%); + } + to { + transform: translateX(0); + } +} + +@keyframes slideInRight { + from { + transform: translateX(100%); + } + to { + transform: translateX(0); + } +} + +@keyframes slideInUp { + from { + transform: translateY(100%); + } + to { + transform: translateY(0); + } +} + +@keyframes slideInDown { + from { + transform: translateY(-100%); + } + to { + transform: translateY(0); + } +} + +/* Special Effects */ +@keyframes shimmer { + 0% { + background-position: -1000px 0; + } + 100% { + background-position: 1000px 0; + } +} + +@keyframes ripple { + 0% { + transform: scale(0); + opacity: 1; + } + 100% { + transform: scale(4); + opacity: 0; + } +} + +@keyframes wave { + 0%, 100% { + transform: translateX(0); + } + 25% { + transform: translateX(-25px); + } + 75% { + transform: translateX(25px); + } +} + +@keyframes float { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-20px); + } +} + +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { + transform: translateY(0); + } + 40% { + transform: translateY(-30px); + } + 60% { + transform: translateY(-15px); + } +} + +/* Loading Animation */ +@keyframes loading { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.loading-spinner { + width: 40px; + height: 40px; + border: 3px solid var(--border); + border-top-color: var(--primary); + border-radius: 50%; + animation: loading 1s linear infinite; +} + +/* Utility Classes */ +.animate-fade-in { + animation: fadeIn 0.5s ease-out; +} + +.animate-fade-up { + animation: fadeInUp 0.6s ease-out; +} + +.animate-fade-up-delay { + animation: fadeInUp 0.6s ease-out 0.2s both; +} + +.animate-fade-up-delay-2 { + animation: fadeInUp 0.6s ease-out 0.4s both; +} + +.animate-scale-in { + animation: scaleIn 0.5s ease-out; +} + +.animate-pulse { + animation: pulse 2s ease-in-out infinite; +} + +.animate-float { + animation: float 3s ease-in-out infinite; +} + +.animate-bounce { + animation: bounce 2s ease-in-out infinite; +} + +/* Scroll Animations */ +.scroll-animate { + opacity: 0; + transform: translateY(30px); + transition: all 0.6s ease-out; +} + +.scroll-animate.visible { + opacity: 1; + transform: translateY(0); +} + +.scroll-animate-left { + opacity: 0; + transform: translateX(-30px); + transition: all 0.6s ease-out; +} + +.scroll-animate-left.visible { + opacity: 1; + transform: translateX(0); +} + +.scroll-animate-right { + opacity: 0; + transform: translateX(30px); + transition: all 0.6s ease-out; +} + +.scroll-animate-right.visible { + opacity: 1; + transform: translateX(0); +} + +.scroll-animate-scale { + opacity: 0; + transform: scale(0.9); + transition: all 0.6s ease-out; +} + +.scroll-animate-scale.visible { + opacity: 1; + transform: scale(1); +} + +/* Parallax Effects */ +.parallax { + transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); +} + +.parallax-slow { + transition: transform 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94); +} + +.parallax-fast { + transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); +} + +/* Hover Effects */ +.hover-lift { + transition: transform 0.3s ease; +} + +.hover-lift:hover { + transform: translateY(-5px); +} + +.hover-grow { + transition: transform 0.3s ease; +} + +.hover-grow:hover { + transform: scale(1.05); +} + +.hover-shrink { + transition: transform 0.3s ease; +} + +.hover-shrink:hover { + transform: scale(0.95); +} + +.hover-rotate { + transition: transform 0.3s ease; +} + +.hover-rotate:hover { + transform: rotate(5deg); +} + +/* Text Effects */ +.text-gradient-animate { + background: linear-gradient( + 90deg, + var(--primary) 0%, + var(--accent) 25%, + var(--primary) 50%, + var(--accent) 75%, + var(--primary) 100% + ); + background-size: 200% auto; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: shimmer 3s linear infinite; +} + +/* Page Transitions */ +.page-transition-fade { + animation: fadeIn 0.5s ease-out; +} + +.page-transition-slide-up { + animation: slideInUp 0.5s ease-out; +} + +.page-transition-slide-left { + animation: slideInLeft 0.5s ease-out; +} + +/* Stagger Animations */ +.stagger-animate > * { + opacity: 0; + transform: translateY(20px); + animation: fadeInUp 0.5s ease-out forwards; +} + +.stagger-animate > *:nth-child(1) { animation-delay: 0.1s; } +.stagger-animate > *:nth-child(2) { animation-delay: 0.2s; } +.stagger-animate > *:nth-child(3) { animation-delay: 0.3s; } +.stagger-animate > *:nth-child(4) { animation-delay: 0.4s; } +.stagger-animate > *:nth-child(5) { animation-delay: 0.5s; } +.stagger-animate > *:nth-child(6) { animation-delay: 0.6s; } + +/* Skeleton Loading */ +.skeleton { + background: linear-gradient( + 90deg, + var(--surface) 25%, + var(--border) 50%, + var(--surface) 75% + ); + background-size: 200% 100%; + animation: shimmer 1.5s infinite; +} + +/* Progress Bar Animation */ +.progress-bar-fill { + animation: fillProgress 2s ease-out forwards; +} + +@keyframes fillProgress { + from { + width: 0%; + } + to { + width: var(--progress, 100%); + } +} + +/* Counter Animation */ +.counter-animate { + animation: countUp 2s ease-out; +} + +@keyframes countUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Water Wave Effect */ +@keyframes waterWave { + 0% { + transform: translateX(0) translateZ(0) scaleY(1); + } + 50% { + transform: translateX(-25%) translateZ(0) scaleY(0.8); + } + 100% { + transform: translateX(-50%) translateZ(0) scaleY(1); + } +} + +.water-wave { + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath d='M321.39,56.44c58-10.79,114.16-30.13,172-41.86,82.39-16.72,168.19-17.73,250.45-.39C823.78,31,906.67,72,985.66,92.83c70.05,18.48,146.53,26.09,214.34,3V0H0V27.35A600.21,600.21,0,0,0,321.39,56.44Z' fill='%23ffffff' fill-opacity='0.3'%3E%3C/path%3E%3C/svg%3E"); + background-size: 200% 100%; + animation: waterWave 10s linear infinite; +} \ No newline at end of file diff --git a/website-mockups/css-backup-20250918-135142/bento-layout.css b/website-mockups/css-backup-20250918-135142/bento-layout.css new file mode 100644 index 0000000..47cf8ea --- /dev/null +++ b/website-mockups/css-backup-20250918-135142/bento-layout.css @@ -0,0 +1,1230 @@ +/* Bento Grid Layout Styles */ + +:root { + --primary-color: #1e3a5f; + --accent-color: #dc2626; + --text-primary: #1e293b; + --text-secondary: #64748b; + --bg-primary: #ffffff; + --bg-secondary: #f8fafc; + --border-color: #e2e8f0; + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1); + --grid-gap: 1.5rem; + --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); + min-height: 100vh; + color: var(--text-primary); + overflow-x: hidden; +} + +/* Minimal Header */ +.minimal-header { + position: fixed; + top: 0; + left: 0; + right: 0; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + z-index: 100; + border-bottom: 1px solid var(--border-color); + transition: var(--transition); +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 2rem; + max-width: 1600px; + margin: 0 auto; +} + +.logo-section { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.logo-img { + width: 40px; + height: 40px; + border-radius: 50%; + object-fit: cover; +} + +.logo-text { + font-size: 1.25rem; + font-weight: 700; + font-family: 'Playfair Display', serif; + color: var(--primary-color); +} + +.header-actions { + display: flex; + align-items: center; + gap: 1rem; +} + +.filter-toggle { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 8px; + cursor: pointer; + transition: var(--transition); + font-size: 0.875rem; + font-weight: 500; +} + +.filter-toggle:hover { + background: var(--primary-color); + color: white; + border-color: var(--primary-color); +} + +.menu-toggle { + display: flex; + flex-direction: column; + gap: 4px; + padding: 0.5rem; + background: transparent; + border: none; + cursor: pointer; +} + +.menu-toggle span { + width: 24px; + height: 2px; + background: var(--text-primary); + transition: var(--transition); +} + +.menu-toggle:hover span { + background: var(--accent-color); +} + +/* Filter Bar */ +.filter-bar { + display: flex; + gap: 0.75rem; + padding: 0 2rem 1rem; + max-width: 1600px; + margin: 0 auto; + overflow-x: auto; + scrollbar-width: none; + opacity: 0; + max-height: 0; + transition: all 0.3s ease; +} + +.filter-bar.active { + opacity: 1; + max-height: 100px; +} + +.filter-bar::-webkit-scrollbar { + display: none; +} + +.filter-chip { + padding: 0.5rem 1rem; + background: white; + border: 1px solid var(--border-color); + border-radius: 20px; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: var(--transition); + white-space: nowrap; +} + +.filter-chip:hover { + background: var(--bg-secondary); + transform: translateY(-2px); +} + +.filter-chip.active { + background: var(--primary-color); + color: white; + border-color: var(--primary-color); +} + +/* Full Screen Menu Overlay */ +.menu-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, var(--primary-color) 0%, #0f172a 100%); + z-index: 200; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + visibility: hidden; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +.menu-overlay.active { + opacity: 1; + visibility: visible; +} + +.menu-content { + text-align: center; + color: white; +} + +.menu-close { + position: absolute; + top: 2rem; + right: 2rem; + background: transparent; + border: none; + color: white; + font-size: 3rem; + cursor: pointer; + transition: var(--transition); +} + +.menu-close:hover { + transform: rotate(90deg); +} + +.menu-nav { + display: flex; + flex-direction: column; + gap: 2rem; +} + +.menu-link { + position: relative; + color: white; + text-decoration: none; + font-size: 3rem; + font-weight: 700; + font-family: 'Playfair Display', serif; + transition: var(--transition); + overflow: hidden; +} + +.menu-link::after { + content: attr(data-text); + position: absolute; + top: 100%; + left: 0; + width: 100%; + color: var(--accent-color); + transition: var(--transition); +} + +.menu-link:hover::after { + top: 0; +} + +.menu-footer { + margin-top: 4rem; + font-size: 0.875rem; + opacity: 0.8; +} + +.menu-contact { + display: flex; + gap: 2rem; + justify-content: center; + margin-top: 1rem; +} + +.menu-contact a { + color: white; + text-decoration: none; + transition: var(--transition); +} + +.menu-contact a:hover { + color: var(--accent-color); +} + +/* Bento Grid Container */ +.bento-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + grid-auto-rows: 250px; + gap: var(--grid-gap); + padding: 120px 2rem 2rem; + max-width: 1600px; + margin: 0 auto; + animation: fadeInUp 0.6s ease; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Bento Tile Base */ +.bento-tile { + background: white; + border-radius: 20px; + overflow: hidden; + position: relative; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + cursor: pointer; + box-shadow: var(--shadow-md); + animation: tileAppear 0.6s ease backwards; +} + +.bento-tile:nth-child(odd) { + animation-delay: 0.1s; +} + +.bento-tile:nth-child(even) { + animation-delay: 0.2s; +} + +@keyframes tileAppear { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} + +.bento-tile:hover { + transform: translateY(-8px) scale(1.02); + box-shadow: var(--shadow-xl); +} + +/* Tile Sizes */ +.tile-small { + grid-column: span 1; + grid-row: span 1; +} + +.tile-medium { + grid-column: span 2; + grid-row: span 1; +} + +.tile-large { + grid-column: span 2; + grid-row: span 2; +} + +@media (max-width: 768px) { + .tile-medium, + .tile-large { + grid-column: span 1; + } + + .tile-large { + grid-row: span 2; + } +} + +/* Hero Tile */ +.tile-hero { + background: linear-gradient(135deg, var(--primary-color) 0%, #0f172a 100%); + color: white; + display: flex; + align-items: center; + padding: 3rem; +} + +.tile-hero .tile-content { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + height: 100%; +} + +.hero-text { + flex: 1; + z-index: 2; +} + +.hero-title { + font-family: 'Playfair Display', serif; + margin-bottom: 1rem; +} + +.hero-subtitle { + display: block; + font-size: 1.25rem; + font-weight: 400; + opacity: 0.9; + margin-bottom: 0.5rem; +} + +.hero-main { + display: block; + font-size: 3rem; + font-weight: 700; + line-height: 1.1; +} + +.hero-description { + font-size: 1.125rem; + opacity: 0.9; + margin-top: 1rem; +} + +.hero-visual { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 50%; + overflow: hidden; + opacity: 0.3; +} + +.hero-video, +.hero-visual img { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* Yacht Tiles */ +.tile-yacht { + padding: 0; +} + +.tile-yacht .tile-content { + height: 100%; + display: flex; + flex-direction: column; +} + +.yacht-badge { + position: absolute; + top: 1rem; + right: 1rem; + background: var(--accent-color); + color: white; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 600; + z-index: 10; +} + +.yacht-image { + position: relative; + height: 60%; + overflow: hidden; +} + +.yacht-image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: var(--transition); +} + +.tile-yacht:hover .yacht-image img { + transform: scale(1.1); +} + +.yacht-overlay { + position: absolute; + inset: 0; + background: linear-gradient(to top, rgba(0,0,0,0.7) 0%, transparent 50%); + display: flex; + align-items: flex-end; + justify-content: center; + padding: 1rem; + opacity: 0; + transition: var(--transition); +} + +.tile-yacht:hover .yacht-overlay { + opacity: 1; +} + +.quick-view-btn { + padding: 0.75rem 1.5rem; + background: white; + border: none; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.quick-view-btn:hover { + background: var(--accent-color); + color: white; +} + +.yacht-info { + padding: 1.5rem; + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.yacht-name { + font-size: 1.25rem; + font-weight: 700; + margin-bottom: 0.5rem; + font-family: 'Playfair Display', serif; +} + +.yacht-specs { + display: flex; + gap: 0.5rem; + font-size: 0.875rem; + color: var(--text-secondary); + margin-bottom: 1rem; +} + +.yacht-explore { + background: transparent; + border: 2px solid var(--primary-color); + color: var(--primary-color); + padding: 0.5rem 1rem; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.yacht-explore:hover { + background: var(--primary-color); + color: white; +} + +/* Service Tiles */ +.tile-service { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 2rem; + display: flex; + align-items: center; + justify-content: center; +} + +.tile-service .tile-content { + text-align: center; +} + +.service-icon { + font-size: 3rem; + margin-bottom: 1rem; +} + +.service-title { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.5rem; + font-family: 'Playfair Display', serif; +} + +.service-desc { + font-size: 0.875rem; + opacity: 0.9; + margin-bottom: 1rem; +} + +.service-link { + color: white; + text-decoration: none; + font-weight: 600; + border-bottom: 2px solid white; + transition: var(--transition); +} + +.service-link:hover { + border-color: transparent; +} + +/* Testimonial Tile */ +.tile-testimonial { + background: var(--bg-secondary); + padding: 1.5rem; + display: flex; + align-items: center; + justify-content: center; +} + +.tile-testimonial .tile-content { + text-align: center; +} + +.stars { + color: #fbbf24; + font-size: 1.25rem; + margin-bottom: 0.5rem; +} + +.testimonial-text { + font-size: 0.875rem; + font-style: italic; + margin-bottom: 0.5rem; + color: var(--text-primary); +} + +.testimonial-author { + font-size: 0.75rem; + font-weight: 600; + color: var(--text-secondary); +} + +/* Destination Tile */ +.tile-destination { + padding: 0; + overflow: hidden; +} + +.destination-image { + position: relative; + height: 100%; +} + +.destination-image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: var(--transition); +} + +.tile-destination:hover .destination-image img { + transform: scale(1.1); +} + +.destination-overlay { + position: absolute; + inset: 0; + background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, transparent 60%); + display: flex; + flex-direction: column; + justify-content: flex-end; + padding: 1.5rem; + color: white; +} + +.destination-name { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.25rem; + font-family: 'Playfair Display', serif; +} + +.destination-desc { + font-size: 0.875rem; + opacity: 0.9; +} + +/* Stat Tile */ +.tile-stat { + background: var(--primary-color); + color: white; + display: flex; + align-items: center; + justify-content: center; + text-align: center; +} + +.stat-number { + font-size: 2.5rem; + font-weight: 800; + margin-bottom: 0.25rem; +} + +.stat-label { + font-size: 0.875rem; + opacity: 0.9; +} + +/* Experience Tile */ +.tile-experience { + padding: 0; +} + +.tile-experience .tile-content { + display: grid; + grid-template-columns: 1fr 1fr; + height: 100%; +} + +.experience-visual { + position: relative; + overflow: hidden; +} + +.experience-visual img { + width: 100%; + height: 100%; + object-fit: cover; + transition: var(--transition); +} + +.tile-experience:hover .experience-visual img { + transform: scale(1.05); +} + +.experience-info { + padding: 2rem; + display: flex; + flex-direction: column; + justify-content: center; +} + +.experience-tag { + display: inline-block; + padding: 0.25rem 0.75rem; + background: var(--accent-color); + color: white; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 600; + margin-bottom: 1rem; + width: fit-content; +} + +.experience-title { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.75rem; + font-family: 'Playfair Display', serif; +} + +.experience-desc { + color: var(--text-secondary); + margin-bottom: 1.5rem; +} + +.experience-btn { + padding: 0.75rem 1.5rem; + background: var(--primary-color); + color: white; + border: none; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.experience-btn:hover { + background: var(--accent-color); +} + +/* Service Mini Tile */ +.tile-service-mini { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); + color: white; + padding: 1.5rem; + display: flex; + align-items: center; + justify-content: center; +} + +.tile-service-mini .tile-content { + text-align: center; +} + +.service-mini-icon { + font-size: 2rem; + margin-bottom: 0.5rem; +} + +.tile-service-mini h4 { + font-size: 1rem; + margin-bottom: 0.5rem; +} + +.mini-link { + color: white; + text-decoration: none; + font-size: 1.5rem; + transition: var(--transition); +} + +.mini-link:hover { + transform: translateX(5px); +} + +/* Video Tile */ +.tile-video { + padding: 0; + position: relative; + overflow: hidden; +} + +.tile-video video { + width: 100%; + height: 100%; + object-fit: cover; +} + +.video-overlay { + position: absolute; + inset: 0; + background: rgba(0,0,0,0.4); + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: var(--transition); +} + +.tile-video:hover .video-overlay { + opacity: 1; +} + +.play-btn { + padding: 1rem 2rem; + background: white; + border: none; + border-radius: 50px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.play-btn:hover { + background: var(--accent-color); + color: white; + transform: scale(1.1); +} + +/* CTA Tile */ +.tile-cta { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 2rem; + display: flex; + align-items: center; + justify-content: center; +} + +.tile-cta .tile-content { + text-align: center; +} + +.cta-title { + font-size: 1.75rem; + font-weight: 700; + margin-bottom: 0.5rem; + font-family: 'Playfair Display', serif; +} + +.cta-desc { + margin-bottom: 1.5rem; + opacity: 0.9; +} + +.cta-buttons { + display: flex; + gap: 1rem; + justify-content: center; +} + +.btn-primary, +.btn-secondary { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.btn-primary { + background: white; + color: var(--primary-color); +} + +.btn-primary:hover { + background: var(--accent-color); + color: white; +} + +.btn-secondary { + background: transparent; + color: white; + border: 2px solid white; +} + +.btn-secondary:hover { + background: white; + color: var(--primary-color); +} + +/* Social Tile */ +.tile-social { + background: linear-gradient(135deg, #e1306c 0%, #fd1d1d 50%, #f77737 100%); + color: white; + padding: 1.5rem; + display: flex; + align-items: center; + justify-content: center; +} + +.tile-social .tile-content { + text-align: center; +} + +.social-icon { + margin-bottom: 0.5rem; +} + +.social-handle { + font-weight: 600; + margin-bottom: 0.5rem; +} + +.social-link { + color: white; + text-decoration: none; + font-weight: 600; + border-bottom: 2px solid white; +} + +/* Weather Tile */ +.tile-weather { + background: linear-gradient(135deg, #89f7fe 0%, #66a6ff 100%); + color: white; + padding: 1.5rem; + display: flex; + align-items: center; + justify-content: center; +} + +.tile-weather .tile-content { + text-align: center; +} + +.weather-icon { + font-size: 2rem; + margin-bottom: 0.5rem; +} + +.weather-temp { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.25rem; +} + +.weather-desc { + font-size: 0.875rem; + opacity: 0.9; +} + +/* Offer Tile */ +.tile-offer { + background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); + color: var(--text-primary); + padding: 2rem; + display: flex; + align-items: center; + justify-content: center; +} + +.offer-badge { + position: absolute; + top: 1rem; + right: 1rem; + background: var(--accent-color); + color: white; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 600; +} + +.offer-title { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.5rem; + font-family: 'Playfair Display', serif; +} + +.offer-desc { + margin-bottom: 1rem; + color: var(--text-secondary); +} + +.offer-btn { + padding: 0.75rem 1.5rem; + background: white; + color: var(--primary-color); + border: none; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.offer-btn:hover { + background: var(--primary-color); + color: white; +} + +/* Floating Action Button */ +.fab-container { + position: fixed; + bottom: 2rem; + right: 2rem; + z-index: 90; +} + +.fab { + width: 60px; + height: 60px; + border-radius: 50%; + background: var(--accent-color); + color: white; + border: none; + box-shadow: var(--shadow-lg); + cursor: pointer; + transition: var(--transition); + display: flex; + align-items: center; + justify-content: center; +} + +.fab:hover { + transform: scale(1.1) rotate(45deg); + background: var(--primary-color); +} + +.fab-menu { + position: absolute; + bottom: 70px; + right: 0; + display: flex; + flex-direction: column; + gap: 0.5rem; + opacity: 0; + visibility: hidden; + transition: var(--transition); +} + +.fab-menu.active { + opacity: 1; + visibility: visible; +} + +.fab-option { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1rem; + background: white; + border: 1px solid var(--border-color); + border-radius: 50px; + box-shadow: var(--shadow-md); + cursor: pointer; + transition: var(--transition); + white-space: nowrap; +} + +.fab-option:hover { + background: var(--primary-color); + color: white; + transform: translateX(-10px); +} + +/* Wishlist Sidebar */ +.wishlist-sidebar { + position: fixed; + top: 0; + right: -400px; + width: 400px; + height: 100vh; + background: white; + box-shadow: var(--shadow-xl); + z-index: 150; + transition: var(--transition); + display: flex; + flex-direction: column; +} + +.wishlist-sidebar.active { + right: 0; +} + +.wishlist-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1.5rem; + border-bottom: 1px solid var(--border-color); +} + +.wishlist-close { + background: transparent; + border: none; + font-size: 2rem; + cursor: pointer; + transition: var(--transition); +} + +.wishlist-close:hover { + transform: rotate(90deg); +} + +.wishlist-content { + flex: 1; + padding: 1.5rem; + overflow-y: auto; +} + +.wishlist-empty { + text-align: center; + color: var(--text-secondary); + padding: 2rem; +} + +.wishlist-footer { + padding: 1.5rem; + border-top: 1px solid var(--border-color); +} + +.wishlist-inquire { + width: 100%; + padding: 1rem; + background: var(--primary-color); + color: white; + border: none; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.wishlist-inquire:hover { + background: var(--accent-color); +} + +/* Layout Switcher */ +.layout-switcher { + position: fixed; + bottom: 2rem; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 0.5rem; + background: white; + padding: 0.5rem; + border-radius: 50px; + box-shadow: var(--shadow-lg); + z-index: 90; +} + +.layout-btn { + padding: 0.5rem 1rem; + background: transparent; + border: none; + border-radius: 50px; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: var(--transition); +} + +.layout-btn:hover { + background: var(--bg-secondary); +} + +.layout-btn.active { + background: var(--primary-color); + color: white; +} + +/* Filter Animations */ +.bento-tile.hiding { + opacity: 0; + transform: scale(0.8); + pointer-events: none; +} + +.bento-tile.showing { + animation: showTile 0.5s ease forwards; +} + +@keyframes showTile { + from { + opacity: 0; + transform: scale(0.8); + } + to { + opacity: 1; + transform: scale(1); + } +} + +/* Responsive Design */ +@media (max-width: 1200px) { + .bento-container { + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + grid-auto-rows: 200px; + } +} + +@media (max-width: 768px) { + .bento-container { + grid-template-columns: 1fr; + padding: 100px 1rem 1rem; + } + + .tile-medium { + grid-column: span 1; + } + + .tile-large { + grid-column: span 1; + grid-row: span 2; + } + + .hero-main { + font-size: 2rem; + } + + .tile-experience .tile-content { + grid-template-columns: 1fr; + } + + .wishlist-sidebar { + width: 100%; + right: -100%; + } + + .layout-switcher { + flex-direction: column; + left: 1rem; + transform: none; + bottom: 5rem; + } +} \ No newline at end of file diff --git a/website-mockups/css-backup-20250918-135142/booking.css b/website-mockups/css-backup-20250918-135142/booking.css new file mode 100644 index 0000000..62c1bd9 --- /dev/null +++ b/website-mockups/css-backup-20250918-135142/booking.css @@ -0,0 +1,586 @@ +/* HarborSmith - Booking Styles */ +/* ============================= */ + +/* Progress Bar */ +.booking-progress { + padding: 100px 0 40px; + background: var(--surface); + border-bottom: 1px solid var(--border); +} + +.progress-steps { + display: flex; + align-items: center; + justify-content: center; + max-width: 600px; + margin: 0 auto; +} + +.progress-step { + display: flex; + flex-direction: column; + align-items: center; + position: relative; +} + +.step-number { + width: 40px; + height: 40px; + border-radius: 50%; + background: var(--background); + border: 2px solid var(--border); + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + color: var(--text-secondary); + transition: all var(--transition-base); +} + +.progress-step.active .step-number { + background: var(--primary); + color: white; + border-color: var(--primary); +} + +.progress-step.completed .step-number { + background: var(--accent); + color: white; + border-color: var(--accent); +} + +.step-label { + margin-top: 8px; + font-size: var(--text-sm); + color: var(--text-secondary); + white-space: nowrap; +} + +.progress-step.active .step-label { + color: var(--primary); + font-weight: 600; +} + +.progress-line { + width: 80px; + height: 2px; + background: var(--border); + margin: 0 10px; + position: relative; + top: -20px; +} + +.progress-step.completed + .progress-line { + background: var(--accent); +} + +/* Booking Section */ +.booking-section { + padding: 60px 0; + min-height: calc(100vh - 200px); +} + +.booking-header { + text-align: center; + margin-bottom: 40px; +} + +.booking-header h1 { + font-size: var(--text-4xl); + margin-bottom: 10px; +} + +.booking-header p { + color: var(--text-secondary); + font-size: var(--text-lg); +} + +.booking-content { + display: grid; + grid-template-columns: 1fr 350px; + gap: 40px; +} + +.booking-main { + display: flex; + flex-direction: column; + gap: 30px; +} + +.booking-card { + background: var(--background); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + padding: 30px; + box-shadow: var(--shadow-sm); +} + +.booking-card h3 { + font-size: var(--text-xl); + margin-bottom: 20px; +} + +/* Calendar */ +.calendar-container { + background: var(--surface); + border-radius: var(--radius-lg); + padding: 20px; +} + +.calendar-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.calendar-nav { + width: 32px; + height: 32px; + border-radius: var(--radius-md); + border: 1px solid var(--border); + background: var(--background); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + transition: all var(--transition-fast); +} + +.calendar-nav:hover { + background: var(--primary); + color: white; + border-color: var(--primary); +} + +.calendar-month { + font-size: var(--text-lg); + font-weight: 600; +} + +.calendar-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 5px; +} + +.calendar-day-header { + text-align: center; + font-size: var(--text-sm); + font-weight: 600; + color: var(--text-secondary); + padding: 10px 0; +} + +.calendar-day { + aspect-ratio: 1; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-md); + cursor: pointer; + font-weight: 500; + transition: all var(--transition-fast); +} + +.calendar-day.other-month { + color: var(--text-secondary); + opacity: 0.3; +} + +.calendar-day.available { + background: var(--background); + color: var(--text); +} + +.calendar-day.available:hover { + background: var(--primary); + color: white; +} + +.calendar-day.unavailable { + background: var(--surface); + color: var(--text-secondary); + cursor: not-allowed; + text-decoration: line-through; +} + +.calendar-day.selected { + background: var(--primary); + color: white; +} + +.calendar-legend { + display: flex; + gap: 20px; + margin-top: 20px; + font-size: var(--text-sm); +} + +.legend-dot { + display: inline-block; + width: 12px; + height: 12px; + border-radius: 50%; + margin-right: 5px; +} + +.legend-dot.available { + background: var(--background); + border: 2px solid var(--primary); +} + +.legend-dot.unavailable { + background: var(--text-secondary); +} + +.legend-dot.selected { + background: var(--primary); +} + +/* Form Elements */ +.form-group { + margin-bottom: 25px; +} + +.form-group label { + display: block; + margin-bottom: 8px; + font-weight: 500; + color: var(--text); +} + +.form-control { + width: 100%; + padding: 12px 15px; + border: 1px solid var(--border); + border-radius: var(--radius-md); + font-size: var(--text-base); + font-family: inherit; + transition: all var(--transition-fast); + background: var(--background); +} + +.form-control:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.1); +} + +.form-text { + display: block; + margin-top: 5px; + font-size: var(--text-sm); + color: var(--text-secondary); +} + +/* Duration Options */ +.duration-options { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 15px; +} + +.duration-option input { + display: none; +} + +.duration-card { + display: flex; + flex-direction: column; + padding: 20px; + border: 2px solid var(--border); + border-radius: var(--radius-lg); + cursor: pointer; + transition: all var(--transition-base); + position: relative; +} + +.duration-option input:checked + .duration-card { + border-color: var(--primary); + background: var(--surface); +} + +.duration-card:hover { + border-color: var(--primary); + transform: translateY(-2px); +} + +.duration-time { + font-size: var(--text-lg); + font-weight: 600; + margin-bottom: 5px; +} + +.duration-price { + color: var(--primary); + font-size: var(--text-xl); + font-weight: 700; +} + +.duration-badge { + position: absolute; + top: 10px; + right: 10px; + padding: 4px 8px; + background: var(--accent); + color: white; + font-size: var(--text-xs); + border-radius: var(--radius-full); + font-weight: 600; +} + +/* Booking Sidebar */ +.booking-sidebar { + display: flex; + flex-direction: column; + gap: 30px; +} + +.booking-summary { + background: var(--surface); + border-radius: var(--radius-xl); + padding: 30px; + position: sticky; + top: 100px; +} + +.booking-summary h3 { + font-size: var(--text-xl); + margin-bottom: 20px; +} + +.summary-item { + display: flex; + justify-content: space-between; + margin-bottom: 15px; + font-size: var(--text-base); +} + +.summary-item span { + color: var(--text-secondary); +} + +.summary-item strong { + color: var(--text); +} + +.summary-divider { + height: 1px; + background: var(--border); + margin: 20px 0; +} + +.summary-total { + font-size: var(--text-lg); +} + +.summary-total strong { + color: var(--primary); + font-size: var(--text-2xl); +} + +.summary-note { + font-size: var(--text-sm); + color: var(--text-secondary); + font-style: italic; + margin-top: 15px; +} + +.booking-help { + background: var(--background); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 20px; + text-align: center; +} + +.booking-help h4 { + font-size: var(--text-lg); + margin-bottom: 10px; +} + +.booking-help p { + color: var(--text-secondary); + margin-bottom: 15px; + font-size: var(--text-sm); +} + +/* Navigation */ +.booking-navigation { + display: flex; + justify-content: space-between; + margin-top: 40px; + padding-top: 30px; + border-top: 1px solid var(--border); +} + +/* Yacht Selection Grid */ +.yacht-selection-grid { + display: grid; + gap: 30px; +} + +.yacht-select-card { + display: grid; + grid-template-columns: 300px 1fr; + background: var(--background); + border: 2px solid var(--border); + border-radius: var(--radius-xl); + overflow: hidden; + transition: all var(--transition-base); + cursor: pointer; +} + +.yacht-select-card:hover { + border-color: var(--primary); + box-shadow: var(--shadow-lg); +} + +.yacht-select-card.selected { + border-color: var(--primary); + background: var(--surface); +} + +.yacht-select-image { + height: 200px; + background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: var(--text-xl); +} + +.yacht-select-info { + padding: 30px; +} + +.yacht-select-header { + display: flex; + justify-content: space-between; + align-items: start; + margin-bottom: 15px; +} + +.yacht-select-name { + font-size: var(--text-2xl); + font-weight: 700; + margin-bottom: 5px; +} + +.yacht-select-type { + color: var(--text-secondary); +} + +.yacht-select-price { + text-align: right; +} + +.yacht-select-specs { + display: flex; + gap: 30px; + margin: 20px 0; +} + +.spec-item { + display: flex; + align-items: center; + gap: 8px; + color: var(--text-secondary); + font-size: var(--text-sm); +} + +/* Customization Options */ +.customization-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; +} + +.addon-card { + border: 2px solid var(--border); + border-radius: var(--radius-lg); + padding: 20px; + transition: all var(--transition-base); + cursor: pointer; +} + +.addon-card:hover { + border-color: var(--primary); +} + +.addon-card.selected { + border-color: var(--primary); + background: var(--surface); +} + +.addon-header { + display: flex; + justify-content: space-between; + align-items: start; + margin-bottom: 10px; +} + +.addon-name { + font-weight: 600; +} + +.addon-price { + color: var(--primary); + font-weight: 700; +} + +.addon-description { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +/* Responsive */ +@media (max-width: 1024px) { + .booking-content { + grid-template-columns: 1fr; + } + + .booking-sidebar { + order: -1; + } + + .booking-summary { + position: static; + } +} + +@media (max-width: 768px) { + .progress-steps { + overflow-x: auto; + padding: 0 20px; + } + + .progress-line { + width: 40px; + } + + .step-label { + font-size: var(--text-xs); + } + + .duration-options { + grid-template-columns: 1fr; + } + + .yacht-select-card { + grid-template-columns: 1fr; + } + + .booking-navigation { + flex-direction: column; + gap: 15px; + } + + .booking-navigation .btn { + width: 100%; + justify-content: center; + } +} \ No newline at end of file diff --git a/website-mockups/css-backup-20250918-135142/maintenance-enhanced.css b/website-mockups/css-backup-20250918-135142/maintenance-enhanced.css new file mode 100644 index 0000000..ff650ef --- /dev/null +++ b/website-mockups/css-backup-20250918-135142/maintenance-enhanced.css @@ -0,0 +1,747 @@ +/* Enhanced Maintenance Portal Styles */ +/* ================================== */ + +:root { + /* Modern Color Palette */ + --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + --secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); + --success-gradient: linear-gradient(135deg, #0ba360 0%, #3cba92 100%); + --warning-gradient: linear-gradient(135deg, #f2994a 0%, #f2c94c 100%); + --danger-gradient: linear-gradient(135deg, #eb3349 0%, #f45c43 100%); + + /* Enhanced shadows */ + --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.06); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08); + --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12); + --shadow-xl: 0 12px 48px rgba(0, 0, 0, 0.16); + --shadow-glow: 0 0 20px rgba(102, 126, 234, 0.4); + + /* Spacing */ + --spacing-xs: 0.25rem; + --spacing-sm: 0.5rem; + --spacing-md: 1rem; + --spacing-lg: 1.5rem; + --spacing-xl: 2rem; + --spacing-2xl: 3rem; + + /* Border radius */ + --radius-sm: 6px; + --radius-md: 12px; + --radius-lg: 16px; + --radius-xl: 20px; + --radius-full: 9999px; + + /* Transitions */ + --transition-fast: 0.15s ease; + --transition-base: 0.3s ease; + --transition-slow: 0.5s ease; +} + +/* Global Enhancements */ +body { + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); + min-height: 100vh; +} + +/* Enhanced Dashboard Cards */ +.dashboard-card, .card, .portal-card { + background: white; + border-radius: var(--radius-lg); + padding: var(--spacing-lg); + box-shadow: var(--shadow-md); + border: 1px solid rgba(255, 255, 255, 0.8); + transition: all var(--transition-base); + position: relative; + overflow: hidden; +} + +.dashboard-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: var(--primary-gradient); + opacity: 0; + transition: opacity var(--transition-base); +} + +.dashboard-card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-xl); +} + +.dashboard-card:hover::before { + opacity: 1; +} + +/* Enhanced Stats Cards */ +.stat-card { + background: white; + border-radius: var(--radius-lg); + padding: var(--spacing-lg); + position: relative; + overflow: hidden; + transition: all var(--transition-base); + border: 1px solid #e5e7eb; +} + +.stat-card::after { + content: ''; + position: absolute; + top: -50%; + right: -50%; + width: 200%; + height: 200%; + background: var(--primary-gradient); + opacity: 0.05; + border-radius: 50%; + transition: all var(--transition-slow); +} + +.stat-card:hover::after { + top: -75%; + right: -75%; + opacity: 0.1; +} + +.stat-card:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); + border-color: #667eea; +} + +.stat-value { + font-size: 2.5rem; + font-weight: 700; + background: var(--primary-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin: var(--spacing-sm) 0; +} + +.stat-label { + color: #6b7280; + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.05em; + font-weight: 600; +} + +.stat-change { + display: inline-flex; + align-items: center; + gap: var(--spacing-xs); + padding: var(--spacing-xs) var(--spacing-sm); + border-radius: var(--radius-full); + font-size: 0.875rem; + font-weight: 600; + margin-top: var(--spacing-sm); +} + +.stat-change.positive { + background: #dcfce7; + color: #16a34a; +} + +.stat-change.negative { + background: #fee2e2; + color: #dc2626; +} + +/* Enhanced Tables */ +.table-container { + background: white; + border-radius: var(--radius-lg); + overflow: hidden; + box-shadow: var(--shadow-md); +} + +table { + width: 100%; + border-collapse: separate; + border-spacing: 0; +} + +thead { + background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%); +} + +thead th { + padding: var(--spacing-md) var(--spacing-lg); + text-align: left; + font-weight: 600; + color: #374151; + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.05em; + border-bottom: 2px solid #e5e7eb; +} + +tbody tr { + transition: all var(--transition-fast); + border-bottom: 1px solid #f3f4f6; +} + +tbody tr:hover { + background: linear-gradient(90deg, rgba(102, 126, 234, 0.05) 0%, rgba(118, 75, 162, 0.05) 100%); +} + +tbody td { + padding: var(--spacing-md) var(--spacing-lg); + color: #4b5563; +} + +/* Enhanced Status Badges */ +.status-badge, .badge { + display: inline-flex; + align-items: center; + gap: var(--spacing-xs); + padding: var(--spacing-xs) var(--spacing-sm); + border-radius: var(--radius-full); + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + transition: all var(--transition-fast); +} + +.status-badge.active, .badge.success { + background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%); + color: #065f46; +} + +.status-badge.scheduled, .badge.warning { + background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); + color: #92400e; +} + +.status-badge.overdue, .badge.danger { + background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%); + color: #dc2626; +} + +.status-badge.completed, .badge.info { + background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); + color: #1e40af; +} + +/* Enhanced Buttons */ +.btn, .portal-btn, button[type="submit"] { + padding: var(--spacing-sm) var(--spacing-lg); + border-radius: var(--radius-md); + font-weight: 600; + transition: all var(--transition-base); + cursor: pointer; + border: none; + display: inline-flex; + align-items: center; + gap: var(--spacing-sm); + position: relative; + overflow: hidden; +} + +.btn::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.3); + transform: translate(-50%, -50%); + transition: width var(--transition-base), height var(--transition-base); +} + +.btn:hover::before { + width: 300px; + height: 300px; +} + +.btn-primary { + background: var(--primary-gradient); + color: white; + box-shadow: 0 4px 6px rgba(102, 126, 234, 0.25); +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.35); +} + +.btn-secondary { + background: white; + color: #4b5563; + border: 2px solid #e5e7eb; +} + +.btn-secondary:hover { + background: #f9fafb; + border-color: #667eea; + color: #667eea; + transform: translateY(-2px); +} + +.btn-success { + background: var(--success-gradient); + color: white; + box-shadow: 0 4px 6px rgba(16, 185, 129, 0.25); +} + +.btn-danger { + background: var(--danger-gradient); + color: white; + box-shadow: 0 4px 6px rgba(239, 68, 68, 0.25); +} + +/* Enhanced Form Elements */ +.form-group { + margin-bottom: var(--spacing-lg); +} + +.form-label { + display: block; + margin-bottom: var(--spacing-sm); + font-weight: 600; + color: #374151; + font-size: 0.875rem; + letter-spacing: 0.025em; +} + +.form-input, .form-select, .form-textarea, +input[type="text"], input[type="email"], input[type="number"], +input[type="date"], input[type="time"], select, textarea { + width: 100%; + padding: var(--spacing-sm) var(--spacing-md); + border: 2px solid #e5e7eb; + border-radius: var(--radius-md); + background: #f9fafb; + transition: all var(--transition-base); + font-family: inherit; +} + +.form-input:focus, .form-select:focus, .form-textarea:focus, +input:focus, select:focus, textarea:focus { + outline: none; + border-color: #667eea; + background: white; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); + transform: translateY(-1px); +} + +/* Enhanced Sidebar */ +.portal-sidebar { + background: linear-gradient(180deg, #1f2937 0%, #111827 100%); + box-shadow: 4px 0 24px rgba(0, 0, 0, 0.1); +} + +.sidebar-nav-item { + margin: var(--spacing-xs) var(--spacing-md); + border-radius: var(--radius-md); + transition: all var(--transition-base); +} + +.sidebar-nav-item a { + display: flex; + align-items: center; + gap: var(--spacing-md); + padding: var(--spacing-sm) var(--spacing-md); + color: #9ca3af; + text-decoration: none; + transition: all var(--transition-base); + position: relative; +} + +.sidebar-nav-item a::before { + content: ''; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 3px; + height: 0; + background: var(--primary-gradient); + transition: height var(--transition-base); + border-radius: 0 3px 3px 0; +} + +.sidebar-nav-item:hover { + background: rgba(255, 255, 255, 0.05); +} + +.sidebar-nav-item:hover a { + color: white; +} + +.sidebar-nav-item.active { + background: rgba(102, 126, 234, 0.1); +} + +.sidebar-nav-item.active a { + color: white; +} + +.sidebar-nav-item.active a::before { + height: 24px; +} + +/* Enhanced Header */ +.portal-header { + background: white; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + border-bottom: 1px solid #e5e7eb; +} + +.header-search { + position: relative; +} + +.header-search input { + padding-left: 2.5rem; + background: #f3f4f6; + border: 2px solid transparent; + border-radius: var(--radius-full); +} + +.header-search input:focus { + background: white; + border-color: #667eea; +} + +.header-search i { + position: absolute; + left: var(--spacing-md); + top: 50%; + transform: translateY(-50%); + color: #9ca3af; +} + +/* Enhanced Charts Container */ +.chart-container { + background: white; + border-radius: var(--radius-lg); + padding: var(--spacing-lg); + box-shadow: var(--shadow-md); + position: relative; +} + +.chart-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--spacing-lg); + padding-bottom: var(--spacing-md); + border-bottom: 2px solid #f3f4f6; +} + +.chart-title { + font-size: 1.125rem; + font-weight: 700; + color: #1f2937; +} + +/* Enhanced Progress Bars */ +.progress-bar { + width: 100%; + height: 8px; + background: #e5e7eb; + border-radius: var(--radius-full); + overflow: hidden; + position: relative; +} + +.progress-bar-fill { + height: 100%; + background: var(--primary-gradient); + border-radius: var(--radius-full); + transition: width var(--transition-slow); + position: relative; + overflow: hidden; +} + +.progress-bar-fill::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.3), + transparent + ); + animation: shimmer 2s infinite; +} + +@keyframes shimmer { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(100%); } +} + +/* Enhanced Notifications */ +.notification { + padding: var(--spacing-md); + border-radius: var(--radius-md); + margin-bottom: var(--spacing-md); + display: flex; + align-items: flex-start; + gap: var(--spacing-md); + animation: slideIn var(--transition-base); +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateX(-20px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.notification.info { + background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%); + border-left: 4px solid #3b82f6; +} + +.notification.success { + background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%); + border-left: 4px solid #22c55e; +} + +.notification.warning { + background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); + border-left: 4px solid #f59e0b; +} + +.notification.error { + background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%); + border-left: 4px solid #ef4444; +} + +/* Enhanced Modals */ +.modal-overlay { + background: rgba(0, 0, 0, 0.5); + -webkit-backdrop-filter: blur(5px); + backdrop-filter: blur(5px); +} + +.modal-content { + background: white; + border-radius: var(--radius-xl); + box-shadow: var(--shadow-xl); + animation: modalSlideIn var(--transition-base); +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: translateY(-30px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +/* Enhanced Timeline */ +.timeline-item { + position: relative; + padding-left: 3rem; + padding-bottom: 2rem; +} + +.timeline-item::before { + content: ''; + position: absolute; + left: 1rem; + top: 0.5rem; + bottom: -2rem; + width: 2px; + background: linear-gradient(180deg, #667eea 0%, #764ba2 100%); +} + +.timeline-item:last-child::before { + display: none; +} + +.timeline-dot { + position: absolute; + left: 0.5rem; + top: 0.5rem; + width: 1rem; + height: 1rem; + background: white; + border: 3px solid #667eea; + border-radius: 50%; + box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1); +} + +/* Enhanced Tooltips */ +.tooltip { + position: relative; +} + +.tooltip::after { + content: attr(data-tooltip); + position: absolute; + bottom: 125%; + left: 50%; + transform: translateX(-50%); + padding: var(--spacing-sm) var(--spacing-md); + background: #1f2937; + color: white; + font-size: 0.875rem; + border-radius: var(--radius-md); + white-space: nowrap; + opacity: 0; + pointer-events: none; + transition: opacity var(--transition-base); + box-shadow: var(--shadow-lg); +} + +.tooltip:hover::after { + opacity: 1; +} + +/* Enhanced Loading States */ +.skeleton { + background: linear-gradient( + 90deg, + #f3f4f6 25%, + #e5e7eb 50%, + #f3f4f6 75% + ); + background-size: 200% 100%; + animation: loading 1.5s infinite; + border-radius: var(--radius-md); +} + +@keyframes loading { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} + +.spinner { + width: 40px; + height: 40px; + border: 4px solid #e5e7eb; + border-top-color: #667eea; + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* Smooth Scrollbar */ +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-track { + background: var(--gray-100); +} + +::-webkit-scrollbar-thumb { + background: var(--gray-400); + border-radius: var(--radius-full); +} + +::-webkit-scrollbar-thumb:hover { + background: var(--gray-500); +} + +/* Focus Visible Enhancement */ +:focus-visible { + outline: 2px solid var(--primary-orange); + outline-offset: 2px; +} + +/* Selection Color */ +::selection { + background: rgba(220, 20, 60, 0.2); + color: var(--gray-900); +} + +/* Responsive Adjustments */ +@media (max-width: 768px) { + .dashboard-grid { + grid-template-columns: 1fr; + } + + .portal-content { + padding: var(--space-md); + } + + table { + font-size: var(--text-sm); + } + + .table-wrapper { + overflow-x: auto; + } +} + +/* Print Styles */ +@media print { + .portal-sidebar, + .portal-header, + .btn, + .no-print { + display: none !important; + } + + .portal-main { + margin-left: 0 !important; + } + + .portal-content { + margin: 0; + padding: 0; + } + + .dashboard-card, + .card { + box-shadow: none; + border: 1px solid var(--gray-300); + page-break-inside: avoid; + } +} + +/* Accessibility Improvements */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* Skip to content link */ +.skip-to-content { + position: absolute; + top: -40px; + left: 0; + background: var(--primary-blue); + color: var(--white); + padding: var(--space-sm) var(--space-md); + text-decoration: none; + z-index: 9999; +} + +.skip-to-content:focus { + top: 0; +} diff --git a/website-mockups/css-backup-20250918-135142/portal-styles.css b/website-mockups/css-backup-20250918-135142/portal-styles.css new file mode 100644 index 0000000..93b45b8 --- /dev/null +++ b/website-mockups/css-backup-20250918-135142/portal-styles.css @@ -0,0 +1,1456 @@ +/* HarborSmith Client Portal Styles */ +/* ================================ */ + +/* CSS Reset and Base Styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* Root Variables */ +:root { + /* Colors */ + --primary-50: #eff6ff; + --primary-100: #dbeafe; + --primary-200: #bfdbfe; + --primary-500: #3b82f6; + --primary-600: #2563eb; + --primary-700: #1d4ed8; + --primary-900: #1e3a8a; + + --gray-50: #f9fafb; + --gray-100: #f3f4f6; + --gray-200: #e5e7eb; + --gray-300: #d1d5db; + --gray-400: #9ca3af; + --gray-500: #6b7280; + --gray-600: #4b5563; + --gray-700: #374151; + --gray-800: #1f2937; + --gray-900: #111827; + + --success-50: #f0fdf4; + --success-100: #dcfce7; + --success-500: #22c55e; + --success-600: #16a34a; + --success-700: #15803d; + + --warning-50: #fffbeb; + --warning-100: #fef3c7; + --warning-500: #f59e0b; + --warning-600: #d97706; + --warning-700: #b45309; + + --danger-50: #fef2f2; + --danger-100: #fee2e2; + --danger-200: #fecaca; + --danger-500: #ef4444; + --danger-600: #dc2626; + --danger-700: #b91c1c; + + --info-50: #eff6ff; + --info-100: #dbeafe; + --info-200: #bfdbfe; + --info-500: #3b82f6; + --info-600: #2563eb; + --info-700: #1d4ed8; + + /* Shadows */ + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1); + + /* Layout */ + --sidebar-width: 260px; + --header-height: 70px; +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + line-height: 1.6; + color: var(--gray-800); + background: var(--gray-50); +} + +/* Portal-specific variables */ +.portal { + --sidebar-width: 280px; + --sidebar-collapsed: 80px; + --header-height: 70px; + --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + --card-shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.12); + --success: #10b981; + --warning: #f59e0b; + --error: #ef4444; + --info: #3b82f6; +} + +/* Portal Layout Structure */ +.portal-wrapper { + min-height: 100vh; + background: var(--soft-cream); + display: flex; +} + +/* Sidebar Navigation */ +.portal-sidebar { + width: var(--sidebar-width); + background: var(--primary-blue); + color: var(--white); + position: fixed; + height: 100vh; + left: 0; + top: 0; + transition: var(--transition); + z-index: 1000; + overflow-y: auto; +} + +.portal-sidebar.collapsed { + width: var(--sidebar-collapsed); +} + +.sidebar-header { + padding: var(--space-md); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + align-items: center; + justify-content: space-between; +} + +.sidebar-logo { + display: flex; + align-items: center; + gap: var(--space-sm); + text-decoration: none; + color: var(--white); +} + +.sidebar-logo img { + width: 40px; + height: 40px; + border-radius: 8px; +} + +.sidebar-logo span { + font-family: var(--font-display); + font-size: 1.25rem; + font-weight: 700; + transition: opacity 0.3s; +} + +.portal-sidebar.collapsed .sidebar-logo span { + opacity: 0; + width: 0; +} + +.sidebar-toggle { + background: none; + border: none; + color: var(--white); + cursor: pointer; + padding: 0.5rem; + border-radius: 4px; + transition: var(--transition); +} + +.sidebar-toggle:hover { + background: rgba(255, 255, 255, 0.1); +} + +/* Sidebar Navigation Menu */ +.sidebar-nav { + padding: var(--space-md) 0; +} + +.sidebar-section { + margin-bottom: var(--space-md); +} + +.sidebar-section-title { + padding: 0 var(--space-md); + margin-bottom: var(--space-sm); + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.1em; + opacity: 0.7; + transition: opacity 0.3s; +} + +.portal-sidebar.collapsed .sidebar-section-title { + opacity: 0; +} + +.sidebar-link { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: 0.75rem var(--space-md); + color: var(--white); + text-decoration: none; + transition: var(--transition); + position: relative; +} + +.sidebar-link:hover { + background: rgba(255, 255, 255, 0.1); +} + +.sidebar-link.active { + background: var(--warm-orange); +} + +.sidebar-link.active::before { + content: ''; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 4px; + background: var(--warm-yellow); +} + +.sidebar-link i { + width: 24px; + height: 24px; + flex-shrink: 0; +} + +.sidebar-link span { + transition: opacity 0.3s; +} + +.portal-sidebar.collapsed .sidebar-link span { + opacity: 0; + width: 0; +} + +/* Main Content Area */ +.portal-main { + margin-left: var(--sidebar-width); + flex: 1; + transition: var(--transition); + min-height: 100vh; +} + +.portal-sidebar.collapsed + .portal-main { + margin-left: var(--sidebar-collapsed); +} + +/* Portal Header */ +.portal-header { + background: var(--white); + height: var(--header-height); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 var(--space-lg); + border-bottom: 1px solid var(--border); + position: sticky; + top: 0; + z-index: 100; +} + +.portal-breadcrumb { + display: flex; + align-items: center; + gap: var(--space-xs); + color: var(--text-light); +} + +.breadcrumb-separator { + margin: 0 var(--space-xs); +} + +.portal-header-actions { + display: flex; + align-items: center; + gap: var(--space-md); +} + +.header-search { + position: relative; +} + +.header-search input { + padding: 0.5rem 1rem 0.5rem 2.5rem; + border: 1px solid var(--border); + border-radius: 8px; + width: 300px; + transition: var(--transition); +} + +.header-search input:focus { + outline: none; + border-color: var(--warm-orange); + box-shadow: 0 0 0 3px rgba(220, 20, 60, 0.1); +} + +.header-search i { + position: absolute; + left: 0.75rem; + top: 50%; + transform: translateY(-50%); + color: var(--text-light); + width: 20px; + height: 20px; +} + +.header-notifications { + position: relative; +} + +.notification-badge { + position: absolute; + top: -4px; + right: -4px; + background: var(--warm-orange); + color: var(--white); + font-size: 0.75rem; + padding: 2px 6px; + border-radius: 10px; +} + +.header-user { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: 0.5rem; + border-radius: 8px; + cursor: pointer; + transition: var(--transition); +} + +.header-user:hover { + background: var(--soft-cream); +} + +.user-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + background: var(--gradient-warm); + display: flex; + align-items: center; + justify-content: center; + color: var(--white); + font-weight: 600; +} + +/* Portal Content */ +.portal-content { + padding: 2rem; + max-width: 1400px; + margin: 0 auto; +} + +.portal-page-header { + margin-bottom: var(--space-lg); +} + +.portal-page-title { + font-family: var(--font-display); + font-size: 2rem; + color: var(--text-dark); + margin-bottom: 0.5rem; +} + +.portal-page-subtitle { + color: var(--text-light); + font-size: 1.1rem; +} + +/* Dashboard Cards */ +.dashboard-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--space-md); + margin-bottom: var(--space-lg); +} + +/* Portal Container */ +.portal-container { + display: flex; + min-height: 100vh; + background: var(--gray-50); +} + +/* Main Content Wrapper */ +.portal-main { + flex: 1; + margin-left: var(--sidebar-width); + transition: margin-left 0.3s ease; +} + +/* Portal Sidebar Styles */ +.portal-sidebar { + width: var(--sidebar-width); + background: #1e3a5f; + position: fixed; + height: 100vh; + left: 0; + top: 0; + z-index: 100; + display: flex; + flex-direction: column; +} + +.sidebar-header { + padding: 1.5rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.sidebar-logo { + height: 40px; + width: auto; +} + +.sidebar-nav { + flex: 1; + padding: 1rem 0; +} + +.nav-item { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1.5rem; + color: rgba(255, 255, 255, 0.8); + text-decoration: none; + transition: all 0.2s; +} + +.nav-item:hover { + background: rgba(255, 255, 255, 0.1); + color: white; +} + +.nav-item.active { + background: rgba(255, 255, 255, 0.15); + color: white; + border-left: 3px solid white; +} + +.nav-icon { + font-size: 1.2rem; + width: 24px; + text-align: center; +} + +.nav-text { + font-size: 0.95rem; +} + +.sidebar-footer { + padding: 1rem 0; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +/* Portal Header */ +.portal-header { + background: white; + padding: 1rem 2rem; + border-bottom: 1px solid var(--gray-200); + display: flex; + align-items: center; + justify-content: space-between; + position: sticky; + top: 0; + z-index: 50; +} + +.header-left { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.header-title { + font-size: 1.5rem; + font-weight: 600; + color: var(--gray-900); + margin: 0; +} + +.breadcrumb { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + color: var(--gray-600); +} + +.breadcrumb a { + color: var(--primary-600); + text-decoration: none; +} + +.breadcrumb a:hover { + text-decoration: underline; +} + +.header-right { + display: flex; + align-items: center; + gap: 1rem; +} + +.icon-button { + position: relative; + background: none; + border: none; + font-size: 1.25rem; + cursor: pointer; + padding: 0.5rem; + border-radius: 8px; + transition: background 0.2s; +} + +.icon-button:hover { + background: var(--gray-100); +} + +.notification-badge { + position: absolute; + top: 0; + right: 0; + background: var(--danger-500); + color: white; + font-size: 0.7rem; + padding: 0.125rem 0.375rem; + border-radius: 10px; + min-width: 18px; + text-align: center; +} + +.user-menu { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.5rem; + border-radius: 8px; + cursor: pointer; + transition: background 0.2s; +} + +.user-menu:hover { + background: var(--gray-100); +} + +.user-avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: var(--primary-500); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; +} + +.user-name { + font-size: 0.9rem; + font-weight: 500; + color: var(--gray-700); +} + +.dropdown-arrow { + font-size: 0.75rem; + color: var(--gray-500); +} + +/* Cards and Content */ +.card { + background: white; + border-radius: 12px; + box-shadow: var(--shadow-sm); + margin-bottom: 1.5rem; + overflow: hidden; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid var(--gray-200); +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; + color: var(--gray-900); + margin: 0; +} + +.card-body { + padding: 1.5rem; +} + +/* Forms */ +.form-group { + margin-bottom: 1.25rem; +} + +.form-group label { + display: block; + margin-bottom: 0.5rem; + font-size: 0.9rem; + font-weight: 500; + color: var(--gray-700); +} + +.form-control { + width: 100%; + padding: 0.625rem 0.875rem; + border: 1px solid var(--gray-300); + border-radius: 8px; + font-size: 0.95rem; + transition: all 0.2s; +} + +.form-control:focus { + outline: none; + border-color: var(--primary-500); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + +.form-row { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1rem; +} + +/* Buttons */ +.btn { + padding: 0.625rem 1.25rem; + border-radius: 8px; + font-size: 0.95rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + border: none; + display: inline-flex; + align-items: center; + gap: 0.5rem; +} + +.btn-primary { + background: var(--primary-600); + color: white; +} + +.btn-primary:hover { + background: var(--primary-700); + transform: translateY(-1px); + box-shadow: var(--shadow-md); +} + +.btn-outline { + background: white; + border: 1px solid var(--gray-300); + color: var(--gray-700); +} + +.btn-outline:hover { + background: var(--gray-50); + border-color: var(--gray-400); +} + +.btn-sm { + padding: 0.375rem 0.75rem; + font-size: 0.875rem; +} + +.btn-lg { + padding: 0.75rem 1.5rem; + font-size: 1rem; +} + +/* Action Buttons */ +.action-buttons { + display: flex; + gap: 0.75rem; + margin-top: 1.5rem; +} + +/* Pagination */ +.pagination { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; + margin-top: 2rem; +} + +.pagination-info { + color: var(--gray-600); + font-size: 0.9rem; +} + +/* Summary Grid */ +.summary-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; + padding: 1rem; + background: var(--gray-50); + border-radius: 8px; +} + +.summary-item { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.summary-label { + font-size: 0.875rem; + color: var(--gray-600); +} + +/* Utility Classes */ +.text-sm { + font-size: 0.875rem; +} + +.text-muted { + color: var(--gray-600); +} + +.text-right { + text-align: right; +} + +/* Checkbox */ +.checkbox-label { + display: flex; + align-items: center; + gap: 0.5rem; + cursor: pointer; + margin-bottom: 0.5rem; +} + +.checkbox-label input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; +} + +/* Sidebar Toggle */ +.sidebar-toggle { + display: none; + background: none; + border: none; + color: white; + cursor: pointer; + padding: 0.5rem; +} + +.sidebar-toggle span { + display: block; + width: 20px; + height: 2px; + background: white; + margin: 4px 0; + transition: all 0.3s; +} + +/* Responsive */ +@media (max-width: 768px) { + .portal-sidebar { + transform: translateX(-100%); + transition: transform 0.3s; + } + + .portal-sidebar.active { + transform: translateX(0); + } + + .portal-main { + margin-left: 0; + } + + .sidebar-toggle { + display: block; + } + + .form-row { + grid-template-columns: 1fr; + } +} + +.dashboard-card { + background: var(--white); + border-radius: 12px; + padding: var(--space-md); + box-shadow: var(--card-shadow); + transition: var(--transition); +} + +.dashboard-card:hover { + box-shadow: var(--card-shadow-hover); + transform: translateY(-2px); +} + +.card-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-md); +} + +.card-title { + font-size: 1.1rem; + font-weight: 600; + color: var(--text-dark); +} + +.card-icon { + width: 48px; + height: 48px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + background: var(--gradient-warm); + color: var(--white); +} + +.card-value { + font-size: 2rem; + font-weight: 700; + color: var(--primary-blue); + margin-bottom: 0.5rem; +} + +.card-change { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.5rem; + border-radius: 4px; + font-size: 0.875rem; + font-weight: 500; +} + +.card-change.positive { + background: rgba(16, 185, 129, 0.1); + color: var(--success); +} + +.card-change.negative { + background: rgba(239, 68, 68, 0.1); + color: var(--error); +} + +/* Status Badges */ +.status-badge { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.875rem; + font-weight: 500; +} + +.status-badge.success { + background: rgba(16, 185, 129, 0.1); + color: var(--success); +} + +.status-badge.warning { + background: rgba(245, 158, 11, 0.1); + color: var(--warning); +} + +.status-badge.error { + background: rgba(239, 68, 68, 0.1); + color: var(--error); +} + +.status-badge.info { + background: rgba(59, 130, 246, 0.1); + color: var(--info); +} + +/* Tables */ +.data-table { + background: var(--white); + border-radius: 12px; + overflow: hidden; + box-shadow: var(--card-shadow); +} + +.table-header { + padding: var(--space-md); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: space-between; +} + +.table-actions { + display: flex; + gap: var(--space-sm); +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background: var(--soft-cream); +} + +th { + text-align: left; + padding: var(--space-sm) var(--space-md); + font-weight: 600; + color: var(--text-dark); + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +td { + padding: var(--space-md); + border-bottom: 1px solid var(--border); + color: var(--text-dark); +} + +tbody tr:hover { + background: var(--soft-cream); +} + +tbody tr:last-child td { + border-bottom: none; +} + +/* Forms */ +.portal-form { + background: var(--white); + border-radius: 12px; + padding: var(--space-lg); + box-shadow: var(--card-shadow); +} + +.form-section { + margin-bottom: var(--space-lg); +} + +.form-section-title { + font-size: 1.25rem; + font-weight: 600; + color: var(--text-dark); + margin-bottom: var(--space-md); + padding-bottom: var(--space-sm); + border-bottom: 2px solid var(--soft-cream); +} + +.form-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: var(--space-md); +} + +.form-group { + margin-bottom: var(--space-md); +} + +.form-label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: var(--text-dark); +} + +.form-label.required::after { + content: ' *'; + color: var(--error); +} + +.form-input, +.form-select, +.form-textarea { + width: 100%; + padding: 0.75rem; + border: 1px solid var(--border); + border-radius: 8px; + font-size: 1rem; + transition: var(--transition); +} + +.form-input:focus, +.form-select:focus, +.form-textarea:focus { + outline: none; + border-color: var(--warm-orange); + box-shadow: 0 0 0 3px rgba(220, 20, 60, 0.1); +} + +.form-textarea { + resize: vertical; + min-height: 120px; +} + +.form-help { + font-size: 0.875rem; + color: var(--text-light); + margin-top: 0.25rem; +} + +.form-error { + font-size: 0.875rem; + color: var(--error); + margin-top: 0.25rem; +} + +/* Buttons */ +.btn-portal { + padding: 0.75rem 1.5rem; + border-radius: 8px; + font-weight: 500; + border: none; + cursor: pointer; + transition: var(--transition); + display: inline-flex; + align-items: center; + gap: 0.5rem; +} + +.btn-portal-primary { + background: var(--gradient-warm); + color: var(--white); +} + +.btn-portal-primary:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(220, 20, 60, 0.3); +} + +.btn-portal-secondary { + background: var(--soft-cream); + color: var(--text-dark); + border: 1px solid var(--border); +} + +.btn-portal-secondary:hover { + background: var(--white); + border-color: var(--warm-orange); +} + +.btn-portal-danger { + background: var(--error); + color: var(--white); +} + +.btn-portal-danger:hover { + background: #dc2626; +} + +/* Upload Area */ +.upload-area { + border: 2px dashed var(--border); + border-radius: 12px; + padding: var(--space-lg); + text-align: center; + transition: var(--transition); + cursor: pointer; +} + +.upload-area:hover { + border-color: var(--warm-orange); + background: var(--soft-cream); +} + +.upload-area.dragging { + border-color: var(--warm-orange); + background: rgba(220, 20, 60, 0.05); +} + +.upload-icon { + width: 48px; + height: 48px; + margin: 0 auto var(--space-sm); + color: var(--text-light); +} + +/* Timeline */ +.timeline { + position: relative; + padding-left: var(--space-lg); +} + +.timeline::before { + content: ''; + position: absolute; + left: 15px; + top: 0; + bottom: 0; + width: 2px; + background: var(--border); +} + +.timeline-item { + position: relative; + margin-bottom: var(--space-md); +} + +.timeline-marker { + position: absolute; + left: -25px; + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--white); + border: 2px solid var(--warm-orange); +} + +.timeline-content { + background: var(--white); + padding: var(--space-md); + border-radius: 8px; + box-shadow: var(--card-shadow); +} + +.timeline-date { + font-size: 0.875rem; + color: var(--text-light); + margin-bottom: 0.5rem; +} + +/* Calendar */ +.calendar-widget { + background: var(--white); + border-radius: 12px; + padding: var(--space-md); + box-shadow: var(--card-shadow); +} + +.calendar-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-md); +} + +.calendar-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 2px; +} + +.calendar-day { + aspect-ratio: 1; + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + cursor: pointer; + transition: var(--transition); +} + +.calendar-day:hover { + background: var(--soft-cream); +} + +.calendar-day.active { + background: var(--gradient-warm); + color: var(--white); +} + +.calendar-day.has-event { + position: relative; +} + +.calendar-day.has-event::after { + content: ''; + position: absolute; + bottom: 4px; + width: 4px; + height: 4px; + border-radius: 50%; + background: var(--primary-orange); +} + +/* Login Page Specific */ +.login-container { + min-height: 100vh; + display: flex; +} + +.login-left { + flex: 1; + position: relative; + overflow: hidden; +} + +.login-video { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; +} + +.login-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, rgba(0, 31, 63, 0.8) 0%, rgba(220, 20, 60, 0.6) 100%); + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + color: var(--white); + padding: var(--space-lg); +} + +.login-brand { + text-align: center; + margin-bottom: var(--space-lg); +} + +.login-brand img { + width: 120px; + height: 120px; + margin-bottom: var(--space-md); + border-radius: 20px; +} + +.login-brand h1 { + font-family: var(--font-display); + font-size: 3rem; + margin-bottom: var(--space-sm); +} + +.login-tagline { + font-size: 1.25rem; + opacity: 0.9; +} + +.login-right { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + background: var(--white); + padding: var(--space-lg); +} + +.login-form-container { + width: 100%; + max-width: 400px; +} + +.login-form-header { + text-align: center; + margin-bottom: var(--space-lg); +} + +.login-form-title { + font-family: var(--font-display); + font-size: 2rem; + color: var(--text-dark); + margin-bottom: 0.5rem; +} + +.login-form-subtitle { + color: var(--text-light); +} + +.role-selector { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-sm); + margin-bottom: var(--space-md); +} + +.role-option { + padding: var(--space-sm); + border: 2px solid var(--border); + border-radius: 8px; + text-align: center; + cursor: pointer; + transition: var(--transition); +} + +.role-option:hover { + border-color: var(--warm-orange); +} + +.role-option.active { + border-color: var(--warm-orange); + background: rgba(220, 20, 60, 0.05); +} + +.divider { + text-align: center; + position: relative; + margin: var(--space-md) 0; +} + +.divider::before { + content: ''; + position: absolute; + top: 50%; + left: 0; + right: 0; + height: 1px; + background: var(--border); +} + +.divider span { + background: var(--white); + padding: 0 var(--space-sm); + position: relative; + color: var(--text-light); +} + +.oauth-buttons { + display: grid; + gap: var(--space-sm); +} + +.oauth-btn { + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-sm); + padding: 0.75rem; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--white); + cursor: pointer; + transition: var(--transition); +} + +.oauth-btn:hover { + background: var(--soft-cream); +} + +/* Responsive Design */ +@media (max-width: 1024px) { + .portal-sidebar { + transform: translateX(-100%); + } + + .portal-sidebar.open { + transform: translateX(0); + } + + .portal-main { + margin-left: 0; + } + + .dashboard-grid { + grid-template-columns: 1fr; + } + + .form-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 768px) { + .login-container { + flex-direction: column; + } + + .login-left { + height: 30vh; + } + + .portal-content { + padding: var(--space-md); + } + + .header-search { + display: none; + } + + .table-wrapper { + overflow-x: auto; + } +} + +/* Loading States */ +.skeleton { + background: linear-gradient(90deg, var(--soft-cream) 25%, var(--white) 50%, var(--soft-cream) 75%); + background-size: 200% 100%; + animation: loading 1.5s infinite; +} + +@keyframes loading { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} + +/* Tooltips */ +.tooltip { + position: relative; +} + +.tooltip::after { + content: attr(data-tooltip); + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + padding: 0.5rem 0.75rem; + background: var(--text-dark); + color: var(--white); + font-size: 0.875rem; + border-radius: 4px; + white-space: nowrap; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s; +} + +.tooltip:hover::after { + opacity: 1; +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fadeIn { + animation: fadeIn 0.5s ease-out; +} + +/* Print Styles */ +@media print { + .portal-sidebar, + .portal-header, + .btn-portal, + .table-actions { + display: none !important; + } + + .portal-main { + margin: 0 !important; + } + + .portal-content { + padding: 0 !important; + } +} diff --git a/website-mockups/css-backup-20250918-135142/styles.css b/website-mockups/css-backup-20250918-135142/styles.css new file mode 100644 index 0000000..5ce4582 --- /dev/null +++ b/website-mockups/css-backup-20250918-135142/styles.css @@ -0,0 +1,1366 @@ +/* HarborSmith - Main Styles */ +/* ========================= */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + + /* Base Colors - will be overridden by themes */ + --primary: #1e3a5f; + --accent: #dc2626; + --background: #ffffff; + --surface: #f8f9fa; + --text: #1f2937; + --text-secondary: #6b7280; + --border: #e5e7eb; + --overlay: rgba(0, 0, 0, 0.5); + + /* Spacing */ + --space-xs: 0.25rem; + --space-sm: 0.5rem; + --space-md: 1rem; + --space-lg: 1.5rem; + --space-xl: 2rem; + --space-2xl: 3rem; + --space-3xl: 4rem; + + /* Typography */ + --text-xs: 0.75rem; + --text-sm: 0.875rem; + --text-base: 1rem; + --text-lg: 1.125rem; + --text-xl: 1.25rem; + --text-2xl: 1.5rem; + --text-3xl: 1.875rem; + --text-4xl: 2.25rem; + --text-5xl: 3rem; + + /* Shadows */ + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1); + + /* Transitions */ + --transition-fast: 150ms ease; + --transition-base: 250ms ease; + --transition-slow: 350ms ease; + + /* Border Radius */ + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + --radius-full: 9999px; +} + +/* Base Styles */ +/* ========================= */ + +html { + scroll-behavior: smooth; +} + +body { + font-family: var(--font-family); + font-size: var(--text-base); + line-height: 1.6; + color: var(--text); + background-color: var(--background); + transition: background-color var(--transition-slow), color var(--transition-slow); +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-lg); +} + +/* Typography */ +/* ========================= */ + +h1, h2, h3, h4, h5, h6 { + font-weight: 700; + line-height: 1.2; + margin-bottom: var(--space-md); +} + +h1 { font-size: var(--text-5xl); } +h2 { font-size: var(--text-4xl); } +h3 { font-size: var(--text-3xl); } +h4 { font-size: var(--text-2xl); } +h5 { font-size: var(--text-xl); } +h6 { font-size: var(--text-lg); } + +p { + margin-bottom: var(--space-md); +} + +a { + color: var(--primary); + text-decoration: none; + transition: color var(--transition-fast); +} + +a:hover { + color: var(--accent); +} + +/* Navigation */ +/* ========================= */ + +.navbar { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + background: rgba(255, 255, 255, 0.98); + backdrop-filter: blur(10px); + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); + transition: all var(--transition-base); +} + +.navbar.scrolled { + background: rgba(255, 255, 255, 1); + backdrop-filter: blur(10px); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); +} + +.nav-container { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-lg) var(--space-xl); + max-width: 1400px; + margin: 0 auto; +} + +.nav-brand { + display: flex; + align-items: center; + gap: var(--space-sm); + font-size: var(--text-xl); + font-weight: 700; + color: var(--primary); +} + +.logo { + color: var(--primary); + border-radius: 50%; + object-fit: cover; +} + +.brand-text { + background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.nav-menu { + display: flex; + align-items: center; + gap: var(--space-xl); +} + +.nav-link { + position: relative; + padding: var(--space-sm) var(--space-md); + font-weight: 500; + color: var(--text); + transition: color var(--transition-fast); + border: none; + background: none; + cursor: pointer; + font-size: var(--text-base); + font-family: inherit; +} + +.nav-link:hover { + color: var(--primary); +} + +.nav-link.active { + color: var(--primary); +} + +.nav-link.active::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 30px; + height: 2px; + background: var(--accent); +} + +/* Mega Menu Dropdown */ +.nav-dropdown { + position: static; +} + +.dropdown-toggle { + display: flex; + align-items: center; + gap: var(--space-xs); +} + +.dropdown-toggle::after { + content: ''; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 5px solid currentColor; + transition: transform var(--transition-fast); +} + +.nav-dropdown:hover .dropdown-toggle::after { + transform: rotate(180deg); +} + +/* Mega Menu Container */ +.mega-menu { + position: absolute; + top: 100%; + left: 0; + right: 0; + background: var(--background); + border-top: 1px solid var(--border); + box-shadow: 0 10px 40px rgba(0,0,0,0.1); + opacity: 0; + visibility: hidden; + transform: translateY(-20px); + transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); + z-index: 1000; +} + +.nav-dropdown:hover .mega-menu { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.mega-menu-content { + max-width: 1200px; + margin: 0 auto; + padding: var(--space-2xl) var(--space-xl); + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: var(--space-3xl); +} + +.mega-menu-section { + display: flex; + flex-direction: column; + gap: var(--space-lg); +} + +.mega-menu-title { + font-size: var(--text-xs); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-secondary); + margin-bottom: var(--space-sm); +} + +.mega-menu-item { + display: flex; + align-items: start; + gap: var(--space-md); + padding: var(--space-md); + border-radius: var(--radius-lg); + color: var(--text); + transition: all var(--transition-fast); + text-decoration: none; +} + +.mega-menu-item:hover { + background: var(--surface); + transform: translateX(5px); +} + +.mega-menu-icon { + font-size: var(--text-3xl); + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + background: var(--surface); + border-radius: var(--radius-lg); + flex-shrink: 0; +} + +.mega-menu-item:hover .mega-menu-icon { + background: var(--primary); + color: white; +} + +.mega-menu-content-wrapper { + flex: 1; +} + +.mega-menu-item-title { + font-weight: 600; + font-size: var(--text-base); + margin-bottom: var(--space-xs); + color: var(--text); +} + +.mega-menu-item-desc { + font-size: var(--text-sm); + color: var(--text-secondary); + line-height: 1.4; +} + +/* Featured Section in Mega Menu */ +.mega-menu-featured { + background: var(--surface); + border-radius: var(--radius-xl); + padding: var(--space-xl); + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.mega-menu-featured-title { + font-size: var(--text-lg); + font-weight: 700; + color: var(--primary); + margin-bottom: var(--space-sm); +} + +.mega-menu-featured-desc { + color: var(--text-secondary); + margin-bottom: var(--space-lg); +} + +.mega-menu-featured .btn { + align-self: flex-start; +} + +/* Theme Switcher */ +.nav-actions { + display: flex; + align-items: center; + gap: var(--space-md); +} + +.theme-switcher { + position: relative; +} + +.theme-btn { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: var(--radius-full); + border: 2px solid var(--border); + background: var(--background); + color: var(--text); + cursor: pointer; + transition: all var(--transition-fast); +} + +.theme-btn:hover { + border-color: var(--primary); + transform: rotate(180deg); +} + +.theme-dropdown { + position: absolute; + top: calc(100% + var(--space-sm)); + right: 0; + min-width: 200px; + background: var(--background); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-xl); + opacity: 0; + visibility: hidden; + transform: translateY(-10px); + transition: all var(--transition-base); + padding: var(--space-sm); +} + +.theme-dropdown.active { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.theme-option { + display: flex; + align-items: center; + gap: var(--space-md); + width: 100%; + padding: var(--space-sm) var(--space-md); + border: none; + background: none; + color: var(--text); + cursor: pointer; + font-size: var(--text-sm); + font-family: inherit; + border-radius: var(--radius-md); + transition: all var(--transition-fast); +} + +.theme-option:hover { + background: var(--surface); +} + +.theme-option.active { + background: var(--primary); + color: white; +} + +.theme-colors { + display: flex; + gap: 2px; +} + +.theme-colors span { + width: 16px; + height: 16px; + border-radius: var(--radius-sm); +} + +/* Mobile Navigation Toggle */ +.nav-toggle { + display: none; + flex-direction: column; + gap: 4px; + padding: var(--space-sm); + background: none; + border: none; + cursor: pointer; +} + +.nav-toggle span { + width: 24px; + height: 2px; + background: var(--text); + transition: all var(--transition-base); +} + +.nav-toggle.active span:nth-child(1) { + transform: rotate(45deg) translate(5px, 5px); +} + +.nav-toggle.active span:nth-child(2) { + opacity: 0; +} + +.nav-toggle.active span:nth-child(3) { + transform: rotate(-45deg) translate(7px, -6px); +} + +/* Hero Section */ +/* ========================= */ + +.hero { + position: relative; + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.hero-background { + position: absolute; + inset: 0; + background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); + background-size: cover; + background-position: center; +} + +.hero-overlay { + position: absolute; + inset: 0; + background: linear-gradient(to bottom, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.5) 100%); +} + +/* Wave animation removed for cleaner design */ + +.hero-content { + position: relative; + z-index: 1; + text-align: center; + color: white; + padding: var(--space-2xl); +} + +.hero-title { + font-size: clamp(var(--text-3xl), 5vw, var(--text-5xl)); + font-weight: 800; + margin-bottom: var(--space-lg); + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); +} + +.hero-subtitle { + display: block; + font-size: var(--text-lg); + font-weight: 400; + opacity: 0.9; + margin-bottom: var(--space-sm); +} + +.hero-description { + font-size: var(--text-xl); + max-width: 600px; + margin: 0 auto var(--space-2xl); + opacity: 0.95; +} + +.hero-actions { + display: flex; + gap: var(--space-lg); + justify-content: center; + flex-wrap: wrap; +} + +.hero-scroll-indicator { + position: absolute; + bottom: var(--space-2xl); + left: 50%; + transform: translateX(-50%); + text-align: center; + color: white; + opacity: 0.8; + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-lg); +} + +.scroll-arrow { + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 8px solid white; + display: block; + animation: bounce 2s infinite; +} + +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } + 40% { transform: translateY(-10px); } + 60% { transform: translateY(-5px); } +} + +/* Buttons */ +/* ========================= */ + +.btn { + display: inline-flex; + align-items: center; + gap: var(--space-sm); + padding: var(--space-sm) var(--space-lg); + font-size: var(--text-base); + font-weight: 600; + font-family: inherit; + border: 2px solid transparent; + border-radius: var(--radius-full); + cursor: pointer; + transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); + text-decoration: none; + white-space: nowrap; + position: relative; + overflow: hidden; + z-index: 1; +} + +/* New modern animations - no ripple effect */ +.btn::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); + transition: left 0.5s; +} + +.btn:hover::before { + left: 100%; +} + +/* Primary button - gradient shift animation */ +.btn-primary { + background: linear-gradient(135deg, var(--primary) 0%, var(--primary) 50%, var(--accent) 50%, var(--accent) 100%); + background-size: 200% 100%; + background-position: 0% 0%; + color: white; + border-color: var(--primary); +} + +.btn-primary:hover { + background-position: 100% 0%; + transform: translateY(-3px) scale(1.02); + box-shadow: 0 10px 20px rgba(0,0,0,0.15); +} + +/* Secondary button - soft lift animation */ +.btn-secondary { + background: white; + color: var(--primary); + border-color: white; + box-shadow: 0 2px 5px rgba(0,0,0,0.1); +} + +.btn-secondary:hover { + background: white; + transform: translateY(-3px) scale(1.02); + box-shadow: 0 8px 20px rgba(0,0,0,0.15); +} + +/* Accent button - glow animation */ +.btn-accent { + background: var(--accent); + color: white; + border-color: var(--accent); + position: relative; +} + +.btn-accent:hover { + transform: translateY(-3px) scale(1.02); + box-shadow: 0 0 20px rgba(var(--accent-rgb, 220,53,69), 0.4), + 0 10px 20px rgba(0,0,0,0.15); +} + +/* Outline button - fill wipe animation */ +.btn-outline { + background: transparent; + color: var(--primary); + border-color: var(--primary); + position: relative; +} + +.btn-outline::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 0; + height: 100%; + background: var(--primary); + transition: width 0.3s ease; + z-index: -1; +} + +.btn-outline:hover { + color: white; + transform: translateY(-2px); +} + +.btn-outline:hover::after { + width: 100%; +} + +/* Outline light button - fill wipe animation */ +.btn-outline-light { + background: transparent; + color: white; + border-color: white; + position: relative; +} + +.btn-outline-light::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 0; + height: 100%; + background: white; + transition: width 0.3s ease; + z-index: -1; +} + +.btn-outline-light:hover { + color: var(--primary); + transform: translateY(-2px); +} + +.btn-outline-light:hover::after { + width: 100%; +} + +.btn-large { + padding: var(--space-md) var(--space-2xl); + font-size: var(--text-lg); +} + +.btn-icon { + transition: transform var(--transition-fast); +} + +.btn:hover .btn-icon { + transform: translateX(4px); +} + +.btn-icon-sm { + width: 16px; + height: 16px; +} + +/* Services Section */ +/* ========================= */ + +.services-split { + padding: var(--space-3xl) 0; + background: var(--surface); +} + +.section-header { + margin-bottom: var(--space-3xl); +} + +.section-title { + font-size: var(--text-4xl); + color: var(--text); + margin-bottom: var(--space-md); +} + +.section-subtitle { + font-size: var(--text-xl); + color: var(--text-secondary); +} + +.text-center { + text-align: center; +} + +.services-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + gap: var(--space-2xl); +} + +.service-card { + background: var(--background); + border-radius: var(--radius-xl); + overflow: hidden; + box-shadow: var(--shadow-lg); + transition: all var(--transition-base); +} + +.service-card:hover { + transform: translateY(-8px); + box-shadow: var(--shadow-xl); +} + +.service-image { + position: relative; + height: 250px; + background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); + background-size: cover; + background-position: center; + display: flex; + align-items: center; + justify-content: center; +} + +.charter-card .service-image { + background: linear-gradient(135deg, #0891b2 0%, #06b6d4 100%); +} + +.maintenance-card .service-image { + background: linear-gradient(135deg, #7c3aed 0%, #a855f7 100%); +} + +.service-overlay { + position: absolute; + inset: 0; + background: radial-gradient(circle at center, transparent 0%, rgba(0, 0, 0, 0.2) 100%); +} + +.service-icon { + position: relative; + z-index: 1; + color: white; + animation: float 3s ease-in-out infinite; +} + +@keyframes float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-10px); } +} + +.service-content { + padding: var(--space-2xl); +} + +.service-title { + font-size: var(--text-2xl); + margin-bottom: var(--space-md); +} + +.service-description { + color: var(--text-secondary); + margin-bottom: var(--space-lg); +} + +.service-features { + list-style: none; + margin-bottom: var(--space-xl); +} + +.service-features li { + position: relative; + padding-left: var(--space-xl); + margin-bottom: var(--space-sm); + color: var(--text-secondary); +} + +.service-features li::before { + content: 'โœ“'; + position: absolute; + left: 0; + color: var(--accent); + font-weight: bold; +} + +/* Stats Section */ +/* ========================= */ + +.stats-section { + padding: var(--space-3xl) 0; + background: var(--surface); + position: relative; + overflow: hidden; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--space-2xl); + position: relative; + z-index: 1; +} + +.stat-card { + text-align: center; + background: white; + padding: var(--space-2xl) var(--space-xl); + border-radius: var(--radius-xl); + border: 1px solid var(--border); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + transition: transform var(--transition-base), box-shadow var(--transition-base); +} + +.stat-card:hover { + transform: translateY(-5px); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); +} + +.stat-number { + font-size: var(--text-4xl); + font-weight: 800; + margin-bottom: var(--space-sm); + color: var(--primary); +} + +.stat-number::after { + content: '+'; +} + +.stat-label { + font-size: var(--text-lg); + color: var(--text-secondary); + font-weight: 500; +} + +/* Yacht Cards */ +/* ========================= */ + +.featured-yachts { + padding: var(--space-3xl) 0; +} + +.yacht-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); + gap: var(--space-2xl); +} + +.yacht-card { + background: var(--background); + border-radius: var(--radius-xl); + overflow: hidden; + box-shadow: var(--shadow-md); + transition: all var(--transition-base); +} + +.yacht-card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-xl); +} + +.yacht-image { + position: relative; + height: 250px; + background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); + background-size: cover; + background-position: center; + display: flex; + align-items: center; + justify-content: center; +} + +.yacht-badge { + position: absolute; + top: var(--space-md); + right: var(--space-md); + padding: var(--space-xs) var(--space-md); + background: var(--accent); + color: white; + font-size: var(--text-sm); + font-weight: 600; + border-radius: var(--radius-full); +} + +.badge-luxury { + background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%); +} + +.badge-value { + background: linear-gradient(135deg, #10b981 0%, #059669 100%); +} + +.yacht-overlay { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: opacity var(--transition-base); +} + +.yacht-card:hover .yacht-overlay { + opacity: 1; +} + +.yacht-view-btn { + padding: var(--space-sm) var(--space-lg); + background: white; + color: var(--primary); + border: none; + border-radius: var(--radius-full); + font-weight: 600; + cursor: pointer; + transition: all var(--transition-base); +} + +.yacht-view-btn:hover { + background: var(--primary); + color: white; + transform: scale(1.05); +} + +.yacht-content { + padding: var(--space-xl); +} + +.yacht-name { + font-size: var(--text-xl); + margin-bottom: var(--space-xs); +} + +.yacht-type { + color: var(--text-secondary); + margin-bottom: var(--space-md); +} + +.yacht-specs { + display: flex; + gap: var(--space-lg); + margin-bottom: var(--space-lg); +} + +.spec { + display: flex; + align-items: center; + gap: var(--space-sm); + font-size: var(--text-sm); + color: var(--text-secondary); +} + +.spec svg { + color: var(--primary); +} + +.spec-icon { + font-size: 1.2em; + color: var(--primary); + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; +} + +.yacht-price { + display: flex; + align-items: baseline; + gap: var(--space-sm); + margin-bottom: var(--space-lg); +} + +.price-label { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +.price-value { + font-size: var(--text-2xl); + font-weight: 700; + color: var(--primary); +} + +/* Testimonials */ +/* ========================= */ + +.testimonials { + padding: var(--space-3xl) 0; + background: var(--surface); +} + +.testimonials-slider { + position: relative; + max-width: 800px; + margin: 0 auto var(--space-2xl); +} + +.testimonial-card { + display: none; + background: var(--background); + border-radius: var(--radius-xl); + padding: var(--space-2xl); + box-shadow: var(--shadow-lg); +} + +.testimonial-card.active { + display: block; + animation: fadeIn var(--transition-slow); +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +.stars { + color: #fbbf24; + font-size: var(--text-xl); + margin-bottom: var(--space-lg); +} + +.testimonial-text { + font-size: var(--text-lg); + line-height: 1.8; + color: var(--text); + margin-bottom: var(--space-xl); + font-style: italic; +} + +.testimonial-author { + display: flex; + align-items: center; + gap: var(--space-md); +} + +.author-avatar { + width: 50px; + height: 50px; + border-radius: var(--radius-full); + background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; +} + +.author-name { + font-weight: 600; + color: var(--text); +} + +.author-title { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +.testimonial-dots { + display: flex; + justify-content: center; + gap: var(--space-sm); +} + +.dot { + width: 10px; + height: 10px; + border-radius: var(--radius-full); + background: var(--border); + border: none; + cursor: pointer; + transition: all var(--transition-base); +} + +.dot.active { + width: 30px; + background: var(--primary); +} + +/* CTA Section */ +/* ========================= */ + +.cta-section { + padding: var(--space-3xl) 0; + background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); +} + +.cta-content { + text-align: center; + color: white; +} + +.cta-title { + font-size: var(--text-4xl); + margin-bottom: var(--space-lg); +} + +.cta-description { + font-size: var(--text-xl); + max-width: 600px; + margin: 0 auto var(--space-2xl); + opacity: 0.95; +} + +.cta-actions { + display: flex; + gap: var(--space-lg); + justify-content: center; + flex-wrap: wrap; +} + +/* Footer */ +/* ========================= */ + +.footer { + background: var(--text); + color: white; + padding: var(--space-3xl) 0 var(--space-xl); +} + +.footer-content { + display: grid; + grid-template-columns: 2fr 1fr 1fr 2fr; + gap: var(--space-3xl); + margin-bottom: var(--space-3xl); +} + +.footer-brand { + display: flex; + align-items: center; + gap: var(--space-sm); + font-size: var(--text-xl); + font-weight: 700; + margin-bottom: var(--space-lg); +} + +.footer-logo { + color: white; +} + +.footer-description { + color: rgba(255, 255, 255, 0.8); + margin-bottom: var(--space-xl); +} + +.footer-social { + display: flex; + gap: var(--space-md); +} + +.social-link { + width: 40px; + height: 40px; + border-radius: var(--radius-full); + background: rgba(255, 255, 255, 0.1); + display: flex; + align-items: center; + justify-content: center; + color: white; + transition: all var(--transition-base); +} + +.social-link:hover { + background: var(--primary); + transform: translateY(-2px); +} + +.footer-title { + font-size: var(--text-lg); + margin-bottom: var(--space-lg); +} + +.footer-links { + list-style: none; +} + +.footer-links li { + margin-bottom: var(--space-sm); +} + +.footer-links a { + color: rgba(255, 255, 255, 0.8); + transition: color var(--transition-fast); +} + +.footer-links a:hover { + color: white; +} + +.footer-contact .contact-item { + display: flex; + align-items: center; + gap: var(--space-sm); + margin-bottom: var(--space-md); + color: rgba(255, 255, 255, 0.8); +} + +.footer-bottom { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: var(--space-2xl); + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.footer-copyright { + color: rgba(255, 255, 255, 0.6); +} + +.footer-legal { + display: flex; + gap: var(--space-xl); +} + +.footer-legal a { + color: rgba(255, 255, 255, 0.6); + transition: color var(--transition-fast); +} + +.footer-legal a:hover { + color: white; +} + +/* Responsive Design */ +/* ========================= */ + +@media (max-width: 1024px) { + .services-grid { + grid-template-columns: 1fr; + } + + .yacht-grid { + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + } + + .footer-content { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 768px) { + .nav-menu { + position: fixed; + top: 0; + right: -100%; + width: 80%; + height: 100vh; + background: var(--background); + flex-direction: column; + align-items: flex-start; + padding: var(--space-3xl) var(--space-xl); + box-shadow: var(--shadow-xl); + transition: right var(--transition-base); + } + + .nav-menu.active { + right: 0; + } + + .nav-toggle { + display: flex; + } + + .hero-title { + font-size: var(--text-3xl); + } + + .hero-description { + font-size: var(--text-lg); + } + + .hero-actions { + flex-direction: column; + align-items: center; + } + + .stats-grid { + grid-template-columns: repeat(2, 1fr); + } + + .footer-content { + grid-template-columns: 1fr; + } + + .footer-bottom { + flex-direction: column; + gap: var(--space-lg); + text-align: center; + } + + .footer-legal { + flex-direction: column; + gap: var(--space-sm); + } +} + +@media (max-width: 480px) { + .container { + padding: 0 var(--space-md); + } + + .services-grid { + grid-template-columns: 1fr; + } + + .yacht-grid { + grid-template-columns: 1fr; + } + + .yacht-specs { + flex-direction: column; + gap: var(--space-sm); + } +} \ No newline at end of file diff --git a/website-mockups/css-backup-20250918-135142/themes.css b/website-mockups/css-backup-20250918-135142/themes.css new file mode 100644 index 0000000..c9398e1 --- /dev/null +++ b/website-mockups/css-backup-20250918-135142/themes.css @@ -0,0 +1,160 @@ +/* HarborSmith - Theme Styles */ +/* ========================= */ + +/* Classical Nautical Theme (Default) - Navy & Crimson */ +[data-theme="nautical"] { + --primary: #001f3f; /* Classic navy blue */ + --accent: #dc143c; /* Nautical red/crimson */ + --background: #ffffff; + --surface: #f0f4f8; + --text: #0a1628; + --text-secondary: #4a5568; + --border: #cbd5e0; + --overlay: rgba(0, 31, 63, 0.7); + --gradient-start: #001f3f; + --gradient-end: #003366; +} + +/* Coastal Dawn Theme - Soft & Serene Luxury */ +[data-theme="coastal-dawn"] { + --primary: #A9B4C2; /* Cadet Blue */ + --accent: #D4AF37; /* Gilded Gold */ + --background: #F8F7F4; /* Alabaster White */ + --surface: #FFFFFF; + --text: #333745; /* Charcoal Slate */ + --text-secondary: #6B7280; + --border: #E5E7EB; + --overlay: rgba(169, 180, 194, 0.4); + --gradient-start: #A9B4C2; + --gradient-end: #C5D3E0; +} + +/* Deep Sea Slate Theme - Modern & Technical */ +[data-theme="deep-sea"] { + --primary: #1E2022; /* Gunmetal Grey */ + --accent: #00BFFF; /* Deep Sky Blue */ + --background: #1E2022; /* Dark background */ + --surface: #2A2D30; + --text: #E5E4E2; /* Platinum text */ + --text-secondary: #C0C0C0; /* Silver */ + --border: #3A3D40; + --overlay: rgba(30, 32, 34, 0.8); + --gradient-start: #1E2022; + --gradient-end: #2A2D30; +} + +/* Monaco White Theme - Pristine & Minimalist */ +[data-theme="monaco-white"] { + --primary: #2C3E50; /* Midnight Blue */ + --accent: #E74C3C; /* Pomegranate Red */ + --background: #FFFFFF; + --surface: #F8F9FA; + --text: #2C3E50; + --text-secondary: #7F8C8D; + --border: #ECF0F1; + --overlay: rgba(44, 62, 80, 0.05); + --gradient-start: #FFFFFF; + --gradient-end: #F8F9FA; +} + +/* Update hero gradient for all themes */ +[data-theme] .hero-background { + background: linear-gradient(135deg, var(--gradient-start) 0%, var(--gradient-end) 100%); +} + +/* Ensure good contrast for nav items */ +[data-theme] .nav-link { + color: var(--text); + font-weight: 500; +} + +[data-theme] .nav-link:hover { + color: var(--accent); +} + +/* Theme switcher button styling */ +[data-theme] .theme-switcher { + background: var(--surface); + border: 2px solid var(--border); + color: var(--text); +} + +[data-theme] .theme-switcher:hover { + background: var(--accent); + color: white; + border-color: var(--accent); +} + +/* Better button contrast */ +[data-theme] .btn-primary { + background: var(--accent); + color: white; + border: 2px solid var(--accent); +} + +[data-theme] .btn-primary:hover { + background: var(--primary); + border-color: var(--primary); +} + +/* Schedule Service button - make it filled */ +[data-theme] .btn-secondary { + background: var(--primary); + color: white; + border: 2px solid var(--primary); +} + +[data-theme] .btn-secondary:hover { + background: var(--accent); + border-color: var(--accent); + color: white; +} + +/* Card improvements */ +[data-theme] .service-card { + background: var(--surface); + border: 1px solid var(--border); +} + +[data-theme] .yacht-card { + background: white; + border: 1px solid var(--border); +} + +/* Stats section contrast */ +[data-theme] .stats-section { + background: var(--surface); +} + +/* Footer styling */ +[data-theme] footer { + background: var(--primary); + color: white; +} + +[data-theme] footer a { + color: rgba(255, 255, 255, 0.8); +} + +[data-theme] footer a:hover { + color: var(--accent); +} + +/* Special styling for Classical Nautical theme */ +[data-theme="nautical"] .btn-primary { + background: var(--accent); + box-shadow: 0 4px 6px rgba(220, 20, 60, 0.2); +} + +[data-theme="nautical"] .btn-secondary { + background: var(--primary); + box-shadow: 0 4px 6px rgba(0, 31, 63, 0.2); +} + +[data-theme="nautical"] .service-card { + border-color: var(--primary); +} + +[data-theme="nautical"] .yacht-card { + border-top: 3px solid var(--accent); +} \ No newline at end of file diff --git a/website-mockups/css-backup-20250918-135142/voyage-layout.css b/website-mockups/css-backup-20250918-135142/voyage-layout.css new file mode 100644 index 0000000..dbd69bc --- /dev/null +++ b/website-mockups/css-backup-20250918-135142/voyage-layout.css @@ -0,0 +1,1642 @@ +/* Voyage Layout - Warm, Cinematic, Inviting */ + +:root { + /* Default/Classical Nautical - Navy & Crimson */ + --primary-blue: #001f3f; + --warm-orange: #dc143c; + --warm-amber: #b91c3c; + --warm-yellow: #ef4444; + --soft-cream: #f0f4f8; + --text-dark: #0a1628; + --text-light: #4a5568; + --white: #ffffff; + --bg-light: #ffffff; + --border: #cbd5e0; + + /* Gradients */ + --gradient-warm: linear-gradient(135deg, #dc143c 0%, #ef4444 100%); + --gradient-sunset: linear-gradient(135deg, #b91c3c 0%, #dc143c 50%, #ef4444 100%); + --gradient-ocean: linear-gradient(135deg, #001f3f 0%, #003366 100%); + + /* Spacing */ + --space-xs: 0.5rem; + --space-sm: 1rem; + --space-md: 2rem; + --space-lg: 3rem; + --space-xl: 4rem; + --space-2xl: 6rem; + + /* Typography */ + --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + --font-display: 'Playfair Display', serif; + + /* Transitions */ + --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Coastal Dawn Theme - Soft, serene, golden accents */ +body.theme-coastal-dawn { + --primary-blue: #A9B4C2; + --warm-orange: #D4AF37; + --warm-amber: #C9A961; + --warm-yellow: #E6D088; + --soft-cream: #F8F7F4; + --text-dark: #333745; + --text-light: #6B7280; + --white: #FFFFFF; + --bg-light: #F8F7F4; + --border: #E5E7EB; + --gradient-warm: linear-gradient(135deg, #D4AF37 0%, #E6D088 100%); + --gradient-sunset: linear-gradient(135deg, #C9A961 0%, #D4AF37 50%, #E6D088 100%); + --gradient-ocean: linear-gradient(135deg, #A9B4C2 0%, #C5D3E0 100%); +} + +/* Deep Sea Theme - Dark, modern, electric blue accents */ +body.theme-deep-sea { + --primary-blue: #1E2022; + --warm-orange: #00BFFF; + --warm-amber: #1E90FF; + --warm-yellow: #4169E1; + --soft-cream: #2A2D30; + --text-dark: #E5E4E2; + --text-light: #C0C0C0; + --white: #1E2022; + --bg-light: #2A2D30; + --border: #3A3D40; + --gradient-warm: linear-gradient(135deg, #00BFFF 0%, #4169E1 100%); + --gradient-sunset: linear-gradient(135deg, #1E90FF 0%, #00BFFF 50%, #4169E1 100%); + --gradient-ocean: linear-gradient(135deg, #1E2022 0%, #2A2D30 100%); +} + +/* Monaco White Theme - Clean, minimalist, red accents */ +body.theme-monaco-white { + --primary-blue: #2C3E50; + --warm-orange: #E74C3C; + --warm-amber: #E67E22; + --warm-yellow: #F39C12; + --soft-cream: #F8F9FA; + --text-dark: #2C3E50; + --text-light: #7F8C8D; + --white: #FFFFFF; + --bg-light: #F8F9FA; + --border: #ECF0F1; + --gradient-warm: linear-gradient(135deg, #E74C3C 0%, #F39C12 100%); + --gradient-sunset: linear-gradient(135deg, #E67E22 0%, #E74C3C 50%, #F39C12 100%); + --gradient-ocean: linear-gradient(135deg, #2C3E50 0%, #34495E 100%); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* Global image fix to ensure images display */ +img { + max-width: 100%; + height: auto; + display: block; +} + +/* Lucide Icons Styling */ +[data-lucide] { + width: 24px; + height: 24px; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; + fill: none; + vertical-align: middle; +} + +.btn-icon { + width: 20px; + height: 20px; + display: inline-block; + margin-right: 8px; +} + +.spec-icon { + width: 20px; + height: 20px; + display: inline-block; + margin-right: 8px; + color: var(--warm-orange); +} + +.feature-icon i { + width: 32px; + height: 32px; + color: var(--warm-orange); +} + +.booking-icon i { + width: 48px; + height: 48px; + color: var(--warm-orange); + margin-bottom: 1rem; +} + +.footer-icon { + width: 16px; + height: 16px; + display: inline-block; + margin-right: 8px; + vertical-align: text-bottom; +} + +.social-links a { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.1); + transition: var(--transition); +} + +.social-links a:hover { + background: var(--warm-orange); +} + +.social-links a i { + width: 20px; + height: 20px; + color: white; +} + +/* Star rating icons */ +.stars-icons { + display: inline-flex; + gap: 2px; +} + +.star-filled { + width: 16px; + height: 16px; + fill: var(--warm-yellow); + stroke: var(--warm-yellow); +} + +body { + font-family: var(--font-sans); + color: var(--text-dark); + background: var(--bg-light); + overflow-x: hidden; + line-height: 1.6; + transition: background 0.3s ease, color 0.3s ease; +} + +/* Smooth Scrolling */ +html { + scroll-behavior: smooth; +} + +/* Navigation */ +.voyage-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + background: rgba(255, 255, 255, 0); + backdrop-filter: blur(0); + transition: var(--transition-slow); + padding: var(--space-md) 0; +} + +.voyage-nav.scrolled { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + padding: var(--space-sm) 0; +} + +.nav-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); + display: flex; + justify-content: space-between; + align-items: center; +} + +.nav-brand { + display: flex; + align-items: center; + gap: var(--space-sm); + font-family: var(--font-display); + font-size: 1.5rem; + font-weight: 700; + color: var(--white); + transition: var(--transition); +} + +.voyage-nav.scrolled .nav-brand { + color: var(--primary-blue); +} + +.nav-logo { + width: 40px; + height: 40px; + border-radius: 50%; + object-fit: cover; +} + +.nav-links { + display: flex; + gap: var(--space-lg); + align-items: center; +} + +.nav-link { + color: var(--white); + text-decoration: none; + font-weight: 500; + transition: var(--transition); + position: relative; +} + +.voyage-nav.scrolled .nav-link { + color: var(--text-dark); +} + +.nav-link::after { + content: ''; + position: absolute; + bottom: -4px; + left: 0; + width: 0; + height: 2px; + background: var(--warm-orange); + transition: var(--transition); +} + +.nav-link:hover::after { + width: 100%; +} + +.nav-cta { + background: var(--gradient-warm); + color: var(--white) !important; + padding: 0.75rem 1.5rem; + border-radius: 50px; + transition: var(--transition); +} + +.nav-cta:hover { + transform: translateY(-2px); + box-shadow: 0 10px 20px rgba(249, 115, 22, 0.3); +} + +/* Theme Switcher */ +.theme-switcher { + position: relative; + margin-left: var(--space-md); +} + +.theme-btn { + background: transparent; + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: var(--transition); + color: var(--white); +} + +.voyage-nav.scrolled .theme-btn { + color: var(--primary-blue); + border-color: var(--primary-blue); +} + +.theme-btn:hover { + background: rgba(255, 255, 255, 0.1); + transform: rotate(180deg); +} + +.voyage-nav.scrolled .theme-btn:hover { + background: rgba(30, 58, 95, 0.1); +} + +.theme-dropdown { + position: absolute; + top: 50px; + right: 0; + background: var(--white); + border-radius: 12px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15); + padding: var(--space-sm); + min-width: 200px; + opacity: 0; + visibility: hidden; + transform: translateY(-10px); + transition: var(--transition); +} + +.theme-dropdown.active { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.theme-option { + display: flex; + align-items: center; + gap: var(--space-sm); + width: 100%; + padding: 0.75rem; + background: transparent; + border: none; + border-radius: 8px; + cursor: pointer; + transition: var(--transition); + text-align: left; +} + +.theme-option:hover { + background: var(--bg-light); +} + +.theme-colors { + display: flex; + gap: 2px; +} + +.theme-colors span { + width: 16px; + height: 16px; + border-radius: 50%; + border: 1px solid rgba(0, 0, 0, 0.1); +} + +/* Hero Section */ +.hero-voyage { + position: relative; + height: 100vh; + min-height: 600px; + overflow: hidden; +} + +.hero-video-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1; +} + +.hero-video { + width: 100%; + height: 100%; + object-fit: cover; +} + +.hero-image-fallback { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + z-index: -1; +} + +.hero-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 2; +} + +.gradient-warm { + background: linear-gradient(to bottom, + rgba(0, 31, 63, 0.3) 0%, + rgba(0, 31, 63, 0.5) 50%, + rgba(0, 31, 63, 0.7) 100%); +} + +.gradient-depth { + background: linear-gradient(to right, + rgba(220, 20, 60, 0.1) 0%, + transparent 100%); +} + +.hero-content { + position: relative; + z-index: 10; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + padding: var(--space-md); +} + +.trust-badge { + display: inline-flex; + align-items: center; + gap: var(--space-sm); + padding: 0.5rem 1.5rem; + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 50px; + color: var(--white) !important; + font-size: 0.875rem; + margin-bottom: var(--space-lg); + z-index: 10; + position: relative; +} + +/* Ensure the trust badge text is white */ +.trust-badge > span:last-child { + color: var(--white) !important; +} + +/* Make sure the headline is visible */ +.hero-headline { + display: block !important; + visibility: visible !important; +} + +.gradient-text { + display: inline-block !important; + visibility: visible !important; +} + +/* Make absolutely sure the trust badge text is white and visible */ +.trust-badge span:last-child { + display: inline !important; + color: white !important; + visibility: visible !important; +} + +.stars { + color: var(--warm-yellow); + font-size: 1rem; +} + +.hero-headline { + font-family: var(--font-display); + font-size: clamp(3rem, 8vw, 6rem); + font-weight: 900; + line-height: 1.1; + margin-bottom: var(--space-md); +} + +.gradient-text { + background: linear-gradient(135deg, + #ffffff 0%, + #f0f4f8 25%, + #e2e8f0 50%, + #cbd5e0 75%, + #94a3b8 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.hero-subtext { + font-size: 1.25rem; + color: rgba(255, 255, 255, 0.9); + max-width: 800px; + margin: 0 auto var(--space-lg); + font-weight: 300; + line-height: 1.8; +} + +.hero-actions { + display: flex; + gap: var(--space-md); + flex-wrap: wrap; + justify-content: center; +} + +.btn-primary-warm, +.btn-secondary-warm { + display: inline-flex; + align-items: center; + gap: var(--space-xs); + padding: 1rem 2rem; + font-size: 1.125rem; + font-weight: 600; + border: none; + border-radius: 50px; + cursor: pointer; + transition: var(--transition); + text-decoration: none; +} + +.btn-primary-warm { + background: var(--gradient-warm); + color: var(--white); + box-shadow: 0 10px 30px rgba(220, 20, 60, 0.3); +} + +.btn-primary-warm:hover { + transform: translateY(-3px); + box-shadow: 0 15px 40px rgba(220, 20, 60, 0.4); +} + +.btn-secondary-warm { + background: rgba(255, 255, 255, 0.1); + color: var(--white); + border: 2px solid rgba(255, 255, 255, 0.3); + backdrop-filter: blur(10px); +} + +.btn-secondary-warm:hover { + background: rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.5); + transform: translateY(-3px); +} + +.btn-icon { + font-size: 1.25rem; +} + +/* Scroll Indicator */ +.scroll-indicator { + position: absolute; + bottom: 2rem; + left: 50%; + transform: translateX(-50%); + color: rgba(255, 255, 255, 0.6); + font-size: 0.875rem; + animation: bounce 2s infinite; +} + +/* Updated scroll arrow styles */ +.scroll-arrow { + margin-top: 0.5rem; + display: flex; + justify-content: center; + align-items: center; +} + +.scroll-arrow i { + width: 32px; + height: 32px; + color: rgba(255, 255, 255, 0.6); + animation: arrow-bounce 1.5s infinite; +} + +@keyframes arrow-bounce { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(8px); + } +} + +/* Decorations */ +.hero-decoration { + position: absolute; + z-index: 5; +} + +.top-right { + top: 5rem; + right: 5rem; +} + +.bottom-left { + bottom: 8rem; + left: 5rem; +} + +.decoration-circle { + width: 80px; + height: 80px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 2rem; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.1); + animation: float 6s ease-in-out infinite; +} + +.decoration-circle.orange { + background: linear-gradient(135deg, + rgba(220, 20, 60, 0.2) 0%, + rgba(239, 68, 68, 0.2) 100%); +} + +.decoration-circle.blue { + background: linear-gradient(135deg, + rgba(0, 31, 63, 0.2) 0%, + rgba(0, 51, 102, 0.2) 100%); +} + +/* Welcome Section */ +.welcome-section { + padding: var(--space-2xl) 0; + background: linear-gradient(to bottom, var(--white) 0%, var(--bg-light) 100%); +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); +} + +.welcome-content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-2xl); + align-items: center; +} + +.section-title { + font-family: var(--font-display); + font-size: 3rem; + font-weight: 700; + margin-bottom: var(--space-md); + color: var(--primary-blue); +} + +.section-title.warm { + background: var(--gradient-warm); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.lead-text { + font-size: 1.25rem; + color: var(--text-light); + margin-bottom: var(--space-lg); + line-height: 1.8; +} + +.feature-list { + display: flex; + flex-direction: column; + gap: var(--space-md); +} + +.feature-item { + display: flex; + gap: var(--space-md); + align-items: flex-start; +} + +.feature-icon { + font-size: 2rem; + background: var(--gradient-warm); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.feature-item h4 { + font-size: 1.25rem; + margin-bottom: 0.25rem; + color: var(--text-dark); +} + +.feature-item p { + color: var(--text-light); +} + +.welcome-image { + position: relative; +} + +.rounded-image { + width: 100%; + border-radius: 20px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1); +} + +.image-badge { + position: absolute; + bottom: 2rem; + right: 2rem; + background: var(--gradient-warm); + color: var(--white); + padding: 1rem 1.5rem; + border-radius: 15px; + font-weight: 600; + display: flex; + flex-direction: column; + align-items: center; + box-shadow: 0 10px 30px rgba(220, 20, 60, 0.3); +} + +/* Fleet Showcase */ +.fleet-showcase { + padding: var(--space-2xl) 0; + background: var(--white); +} + +.section-header { + text-align: center; + margin-bottom: var(--space-2xl); +} + +.section-subtitle { + font-size: 1.25rem; + color: var(--text-light); + margin-top: var(--space-sm); +} + +.fleet-carousel { + position: relative; + max-width: 1000px; + margin: 0 auto; +} + +.yacht-card { + display: none; + grid-template-columns: 1fr 1fr; + gap: var(--space-lg); + align-items: center; + background: var(--white); + border-radius: 20px; + overflow: hidden; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08); +} + +.yacht-card.active { + display: grid; + animation: fadeIn 0.6s ease; +} + +.yacht-image-container { + position: relative; + height: 500px; + overflow: hidden; +} + +.yacht-image-container img { + width: 100%; + height: 100%; + object-fit: cover; + transition: var(--transition-slow); +} + +.yacht-card:hover .yacht-image-container img { + transform: scale(1.05); +} + +.yacht-badge { + position: absolute; + top: 2rem; + left: 2rem; + padding: 0.5rem 1rem; + border-radius: 50px; + font-weight: 600; + font-size: 0.875rem; + backdrop-filter: blur(10px); +} + +.yacht-badge.premium { + background: var(--gradient-warm); + color: var(--white); +} + +.yacht-badge.adventure { + background: var(--gradient-ocean); + color: var(--white); +} + +.yacht-details { + padding: var(--space-lg); +} + +.yacht-name { + font-family: var(--font-display); + font-size: 2.5rem; + margin-bottom: var(--space-sm); + color: var(--primary-blue); +} + +.yacht-description { + color: var(--text-light); + margin-bottom: var(--space-md); + line-height: 1.8; +} + +.yacht-specs { + display: flex; + flex-direction: column; + gap: var(--space-sm); + margin-bottom: var(--space-md); +} + +.spec { + display: flex; + align-items: center; + gap: var(--space-sm); +} + +.spec-icon { + font-size: 1.25rem; +} + +.yacht-pricing { + display: flex; + align-items: baseline; + gap: var(--space-xs); + margin-bottom: var(--space-md); +} + +.price-from { + color: var(--text-light); + font-size: 0.875rem; +} + +.price-amount { + font-size: 2rem; + font-weight: 700; + color: var(--warm-orange); +} + +.btn-book-yacht { + width: 100%; + padding: 1rem; + background: var(--gradient-warm); + color: var(--white); + border: none; + border-radius: 12px; + font-size: 1.125rem; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.btn-book-yacht:hover { + transform: translateY(-2px); + box-shadow: 0 10px 30px rgba(220, 20, 60, 0.3); +} + +.fleet-nav { + display: flex; + justify-content: center; + align-items: center; + gap: var(--space-md); + margin-top: var(--space-lg); +} + +.fleet-prev, +.fleet-next { + width: 50px; + height: 50px; + border-radius: 50%; + border: 2px solid var(--warm-orange); + background: var(--white); + color: var(--warm-orange); + font-size: 1.5rem; + cursor: pointer; + transition: var(--transition); +} + +.fleet-prev:hover, +.fleet-next:hover { + background: var(--gradient-warm); + color: var(--white); + border-color: transparent; +} + +.fleet-dots { + display: flex; + gap: var(--space-sm); +} + +.dot { + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--text-light); + opacity: 0.3; + cursor: pointer; + transition: var(--transition); +} + +.dot.active { + background: var(--warm-orange); + opacity: 1; + transform: scale(1.2); +} + +/* Services Section */ +.services-section { + padding: var(--space-2xl) 0; + background: linear-gradient(135deg, var(--soft-cream) 0%, var(--white) 100%); +} + +.services-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: var(--space-lg); + margin: var(--space-xl) 0; +} + +.service-card { + background: var(--white); + border-radius: 20px; + padding: var(--space-xl); + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08); + transition: var(--transition); + position: relative; + overflow: hidden; +} + +.service-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 5px; + background: var(--gradient-warm); +} + +.charter-service::before { + background: var(--gradient-ocean); +} + +.service-card:hover { + transform: translateY(-5px); + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.12); +} + +.service-icon-wrapper { + width: 80px; + height: 80px; + background: var(--gradient-warm); + border-radius: 20px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: var(--space-md); +} + +.charter-service .service-icon-wrapper { + background: var(--gradient-ocean); +} + +.service-icon { + width: 40px; + height: 40px; + color: white; +} + +.service-card h3 { + font-family: var(--font-display); + font-size: 2rem; + margin-bottom: var(--space-sm); + color: var(--text-dark); +} + +.service-card p { + color: var(--text-light); + margin-bottom: var(--space-md); + line-height: 1.6; +} + +.service-features { + list-style: none; + padding: 0; + margin: var(--space-md) 0; +} + +.service-features li { + padding: 0.5rem 0; + display: flex; + align-items: center; + color: var(--text-dark); +} + +.check-icon { + width: 20px; + height: 20px; + color: var(--warm-orange); + margin-right: 0.75rem; + flex-shrink: 0; +} + +.btn-service { + width: 100%; + padding: 1rem 2rem; + background: var(--gradient-warm); + color: white; + border: none; + border-radius: 12px; + font-weight: 600; + font-size: 1rem; + cursor: pointer; + transition: var(--transition); + margin-top: var(--space-md); +} + +.charter-service .btn-service { + background: var(--gradient-ocean); +} + +.btn-service:hover { + transform: scale(1.02); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); +} + +/* Service Stats */ +.service-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--space-md); + margin-top: var(--space-xl); + padding: var(--space-lg); + background: white; + border-radius: 20px; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05); +} + +.stat-item { + text-align: center; + padding: var(--space-md); +} + +.stat-icon { + width: 40px; + height: 40px; + color: var(--warm-orange); + margin-bottom: var(--space-sm); +} + +.stat-number { + display: block; + font-size: 2.5rem; + font-weight: 800; + color: var(--primary-blue); + font-family: var(--font-display); +} + +.stat-label { + display: block; + color: var(--text-light); + font-size: 0.875rem; + margin-top: 0.5rem; +} + +/* Experience Stories */ +.experience-stories { + padding: var(--space-2xl) 0; + background: var(--bg-light); +} + +.story-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); +} + +.section-title.center { + text-align: center; + margin-bottom: var(--space-2xl); +} + +.stories-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: var(--space-lg); +} + +.story-card { + background: var(--white); + border-radius: 20px; + overflow: hidden; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); + transition: var(--transition); +} + +.story-card:hover { + transform: translateY(-5px); + box-shadow: 0 15px 40px rgba(0, 0, 0, 0.12); +} + +.story-image { + position: relative; + height: 250px; + overflow: hidden; +} + +.story-image img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.story-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(to bottom, transparent 0%, rgba(0, 0, 0, 0.5) 100%); + display: flex; + align-items: flex-end; + padding: var(--space-md); +} + +.story-category { + background: var(--gradient-warm); + color: var(--white); + padding: 0.5rem 1rem; + border-radius: 50px; + font-size: 0.875rem; + font-weight: 600; +} + +.story-content { + padding: var(--space-md); +} + +.story-content h3 { + font-family: var(--font-display); + font-size: 1.5rem; + margin-bottom: var(--space-sm); + color: var(--text-dark); +} + +.story-content p { + color: var(--text-light); + line-height: 1.6; + margin-bottom: var(--space-sm); +} + +.story-link { + color: var(--warm-orange); + text-decoration: none; + font-weight: 600; + transition: var(--transition); +} + +.story-link:hover { + color: var(--warm-amber); +} + +/* Gallery Section */ +.gallery-section { + padding: var(--space-2xl) 0; + background: var(--white); +} + +.image-gallery { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--space-md); + margin-top: var(--space-lg); +} + +.gallery-item { + position: relative; + border-radius: 15px; + overflow: hidden; + height: 300px; + cursor: pointer; + transition: var(--transition); +} + +.gallery-item.large { + grid-column: span 2; + height: 400px; +} + +.gallery-item img { + width: 100%; + height: 100%; + object-fit: cover; + transition: var(--transition-slow); +} + +.gallery-overlay { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: linear-gradient(to top, rgba(0, 31, 63, 0.9) 0%, transparent 100%); + padding: var(--space-lg) var(--space-md) var(--space-md); + transform: translateY(100%); + transition: var(--transition); +} + +.gallery-caption { + color: var(--white); + font-size: 1.25rem; + font-weight: 600; +} + +.gallery-item:hover .gallery-overlay { + transform: translateY(0); +} + +.gallery-item:hover img { + transform: scale(1.1); +} + +@media (max-width: 768px) { + .gallery-item.large { + grid-column: span 1; + height: 300px; + } +} + +/* Booking CTA */ +.booking-cta { + padding: var(--space-2xl) 0; + background: linear-gradient(135deg, #001f3f 0%, #003366 100%); +} + +.booking-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); +} + +.booking-content { + text-align: center; +} + +.booking-title { + font-family: var(--font-display); + font-size: 3rem; + color: var(--white); + margin-bottom: var(--space-sm); +} + +.booking-subtitle { + font-size: 1.25rem; + color: rgba(255, 255, 255, 0.8); + margin-bottom: var(--space-2xl); +} + +.booking-options { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--space-lg); +} + +.booking-card { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 20px; + padding: var(--space-lg); + text-align: center; + transition: var(--transition); +} + +.booking-card:hover { + background: rgba(255, 255, 255, 0.15); + transform: translateY(-5px); +} + +.booking-card.featured { + background: var(--gradient-warm); + border: none; + transform: scale(1.05); +} + +.booking-icon { + font-size: 3rem; + margin-bottom: var(--space-sm); +} + +.booking-card h3 { + font-size: 1.5rem; + color: var(--white); + margin-bottom: var(--space-xs); +} + +.booking-card p { + color: rgba(255, 255, 255, 0.8); + margin-bottom: var(--space-md); +} + +.btn-booking { + width: 100%; + padding: 0.75rem 1.5rem; + background: rgba(255, 255, 255, 0.2); + color: var(--white); + border: 2px solid var(--white); + border-radius: 50px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.btn-booking:hover { + background: var(--white); + color: var(--primary-blue); +} + +.btn-booking.primary { + background: var(--white); + color: var(--warm-orange); + border-color: var(--white); +} + +.btn-booking.primary:hover { + background: var(--gradient-warm); + color: var(--white); +} + +/* Footer */ +.voyage-footer { + background: var(--primary-blue); + color: var(--white); + padding: var(--space-2xl) 0 var(--space-md); +} + +.footer-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); +} + +.footer-content { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + gap: var(--space-2xl); + margin-bottom: var(--space-lg); +} + +.footer-brand h3 { + font-family: var(--font-display); + font-size: 2rem; + margin-bottom: var(--space-sm); +} + +.footer-logo { + width: 50px; + height: 50px; + border-radius: 50%; + margin-bottom: var(--space-sm); +} + +.footer-brand p { + color: rgba(255, 255, 255, 0.7); + margin-bottom: var(--space-md); +} + +.social-links { + display: flex; + gap: var(--space-sm); +} + +.social-links a { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.1); + border-radius: 50%; + font-size: 1.25rem; + transition: var(--transition); +} + +.social-links a:hover { + background: var(--gradient-warm); + transform: translateY(-3px); +} + +.footer-links h4, +.footer-contact h4 { + margin-bottom: var(--space-md); +} + +.footer-links a { + display: block; + color: rgba(255, 255, 255, 0.7); + text-decoration: none; + margin-bottom: var(--space-xs); + transition: var(--transition); +} + +.footer-links a:hover { + color: var(--warm-orange); +} + +.footer-contact p { + color: rgba(255, 255, 255, 0.7); + margin-bottom: var(--space-xs); +} + +.footer-bottom { + text-align: center; + padding-top: var(--space-lg); + border-top: 1px solid rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.5); +} + +/* Layout Switcher */ +.layout-switcher { + position: fixed; + bottom: 2rem; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 0.5rem; + background: var(--white); + padding: 0.5rem; + border-radius: 50px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); + z-index: 90; +} + +.layout-btn { + padding: 0.5rem 1rem; + background: transparent; + border: none; + border-radius: 50px; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: var(--transition); + color: var(--text-dark); +} + +.layout-btn:hover { + background: var(--bg-light); +} + +.layout-btn.active { + background: var(--gradient-warm); + color: var(--white); +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { + transform: translateX(-50%) translateY(0); + } + 40% { + transform: translateX(-50%) translateY(-10px); + } + 60% { + transform: translateX(-50%) translateY(-5px); + } +} + +@keyframes scroll { + 0% { + top: 8px; + opacity: 1; + } + 100% { + top: 20px; + opacity: 0; + } +} + +@keyframes float { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-20px); + } +} + +.animate-fade-in { + animation: fadeIn 0.8s ease; +} + +.animate-fade-up { + animation: fadeIn 0.8s ease 0.2s both; +} + +.animate-fade-up-delay { + animation: fadeIn 0.8s ease 0.4s both; +} + +.animate-fade-up-delay-2 { + animation: fadeIn 0.8s ease 0.6s both; +} + +/* Responsive */ +@media (max-width: 768px) { + .hero-headline { + font-size: 3rem; + } + + .hero-subtext { + font-size: 1rem; + } + + .hero-actions { + flex-direction: column; + width: 100%; + padding: 0 var(--space-md); + } + + .btn-primary-warm, + .btn-secondary-warm { + width: 100%; + } + + .welcome-content { + grid-template-columns: 1fr; + } + + .yacht-card { + grid-template-columns: 1fr; + } + + .footer-content { + grid-template-columns: 1fr; + text-align: center; + } + + .social-links { + justify-content: center; + } + + .top-right, + .bottom-left { + display: none; + } + + .nav-links { + display: none; + } +} + +/* Dark theme adjustments for Deep Sea */ +body.theme-deep-sea .welcome-section, +body.theme-deep-sea .fleet-showcase, +body.theme-deep-sea .experience-stories, +body.theme-deep-sea .booking-cta { + background: var(--bg-light); +} + +body.theme-deep-sea .yacht-card, +body.theme-deep-sea .story-card, +body.theme-deep-sea .booking-card { + background: var(--soft-cream); + color: var(--text-dark); +} + +body.theme-deep-sea .hero-content h1, +body.theme-deep-sea .hero-content p { + color: var(--text-dark); +} + +body.theme-deep-sea .voyage-nav:not(.scrolled) .nav-brand, +body.theme-deep-sea .voyage-nav:not(.scrolled) .nav-link { + color: var(--text-dark); +} + +body.theme-deep-sea .voyage-footer { + background: var(--primary-blue); + color: var(--text-dark); +} + +/* Theme-specific button colors */ +body.theme-coastal-dawn .btn-primary-warm, +body.theme-coastal-dawn .btn-secondary-warm { + background: var(--warm-orange); + color: var(--white); +} + +body.theme-deep-sea .btn-primary-warm, +body.theme-deep-sea .btn-secondary-warm { + background: var(--warm-orange); + color: var(--white); +} + +body.theme-monaco-white .btn-primary-warm, +body.theme-monaco-white .btn-secondary-warm { + background: var(--warm-orange); + color: var(--white); +} \ No newline at end of file diff --git a/website-mockups/css/animations.css b/website-mockups/css/animations.css new file mode 100644 index 0000000..702c2b2 --- /dev/null +++ b/website-mockups/css/animations.css @@ -0,0 +1,435 @@ +/* HarborSmith - Animation Styles */ +/* ========================= */ + +/* Fade Animations */ +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInDown { + from { + opacity: 0; + transform: translateY(-30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInLeft { + from { + opacity: 0; + transform: translateX(-30px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes fadeInRight { + from { + opacity: 0; + transform: translateX(30px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +/* Scale Animations */ +@keyframes scaleIn { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes pulse { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } +} + +/* Rotate Animations */ +@keyframes rotate { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +/* Slide Animations */ +@keyframes slideInLeft { + from { + transform: translateX(-100%); + } + to { + transform: translateX(0); + } +} + +@keyframes slideInRight { + from { + transform: translateX(100%); + } + to { + transform: translateX(0); + } +} + +@keyframes slideInUp { + from { + transform: translateY(100%); + } + to { + transform: translateY(0); + } +} + +@keyframes slideInDown { + from { + transform: translateY(-100%); + } + to { + transform: translateY(0); + } +} + +/* Special Effects */ +@keyframes shimmer { + 0% { + background-position: -1000px 0; + } + 100% { + background-position: 1000px 0; + } +} + +@keyframes ripple { + 0% { + transform: scale(0); + opacity: 1; + } + 100% { + transform: scale(4); + opacity: 0; + } +} + +@keyframes wave { + 0%, 100% { + transform: translateX(0); + } + 25% { + transform: translateX(-25px); + } + 75% { + transform: translateX(25px); + } +} + +@keyframes float { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-20px); + } +} + +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { + transform: translateY(0); + } + 40% { + transform: translateY(-30px); + } + 60% { + transform: translateY(-15px); + } +} + +/* Loading Animation */ +@keyframes loading { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.loading-spinner { + width: 40px; + height: 40px; + border: 3px solid var(--border); + border-top-color: var(--primary); + border-radius: 50%; + animation: loading 1s linear infinite; +} + +/* Utility Classes */ +.animate-fade-in { + animation: fadeIn 0.5s ease-out; +} + +.animate-fade-up { + animation: fadeInUp 0.6s ease-out; +} + +.animate-fade-up-delay { + animation: fadeInUp 0.6s ease-out 0.2s both; +} + +.animate-fade-up-delay-2 { + animation: fadeInUp 0.6s ease-out 0.4s both; +} + +.animate-scale-in { + animation: scaleIn 0.5s ease-out; +} + +.animate-pulse { + animation: pulse 2s ease-in-out infinite; +} + +.animate-float { + animation: float 3s ease-in-out infinite; +} + +.animate-bounce { + animation: bounce 2s ease-in-out infinite; +} + +/* Scroll Animations */ +.scroll-animate { + opacity: 0; + transform: translateY(30px); + transition: all 0.6s ease-out; +} + +.scroll-animate.visible { + opacity: 1; + transform: translateY(0); +} + +.scroll-animate-left { + opacity: 0; + transform: translateX(-30px); + transition: all 0.6s ease-out; +} + +.scroll-animate-left.visible { + opacity: 1; + transform: translateX(0); +} + +.scroll-animate-right { + opacity: 0; + transform: translateX(30px); + transition: all 0.6s ease-out; +} + +.scroll-animate-right.visible { + opacity: 1; + transform: translateX(0); +} + +.scroll-animate-scale { + opacity: 0; + transform: scale(0.9); + transition: all 0.6s ease-out; +} + +.scroll-animate-scale.visible { + opacity: 1; + transform: scale(1); +} + +/* Parallax Effects */ +.parallax { + transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); +} + +.parallax-slow { + transition: transform 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94); +} + +.parallax-fast { + transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); +} + +/* Hover Effects */ +.hover-lift { + transition: transform 0.3s ease; +} + +.hover-lift:hover { + transform: translateY(-5px); +} + +.hover-grow { + transition: transform 0.3s ease; +} + +.hover-grow:hover { + transform: scale(1.05); +} + +.hover-shrink { + transition: transform 0.3s ease; +} + +.hover-shrink:hover { + transform: scale(0.95); +} + +.hover-rotate { + transition: transform 0.3s ease; +} + +.hover-rotate:hover { + transform: rotate(5deg); +} + +/* Text Effects */ +.text-gradient-animate { + background: linear-gradient( + 90deg, + var(--primary) 0%, + var(--accent) 25%, + var(--primary) 50%, + var(--accent) 75%, + var(--primary) 100% + ); + background-size: 200% auto; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: shimmer 3s linear infinite; +} + +/* Page Transitions */ +.page-transition-fade { + animation: fadeIn 0.5s ease-out; +} + +.page-transition-slide-up { + animation: slideInUp 0.5s ease-out; +} + +.page-transition-slide-left { + animation: slideInLeft 0.5s ease-out; +} + +/* Stagger Animations */ +.stagger-animate > * { + opacity: 0; + transform: translateY(20px); + animation: fadeInUp 0.5s ease-out forwards; +} + +.stagger-animate > *:nth-child(1) { animation-delay: 0.1s; } +.stagger-animate > *:nth-child(2) { animation-delay: 0.2s; } +.stagger-animate > *:nth-child(3) { animation-delay: 0.3s; } +.stagger-animate > *:nth-child(4) { animation-delay: 0.4s; } +.stagger-animate > *:nth-child(5) { animation-delay: 0.5s; } +.stagger-animate > *:nth-child(6) { animation-delay: 0.6s; } + +/* Skeleton Loading */ +.skeleton { + background: linear-gradient( + 90deg, + var(--surface) 25%, + var(--border) 50%, + var(--surface) 75% + ); + background-size: 200% 100%; + animation: shimmer 1.5s infinite; +} + +/* Progress Bar Animation */ +.progress-bar-fill { + animation: fillProgress 2s ease-out forwards; +} + +@keyframes fillProgress { + from { + width: 0%; + } + to { + width: var(--progress, 100%); + } +} + +/* Counter Animation */ +.counter-animate { + animation: countUp 2s ease-out; +} + +@keyframes countUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Water Wave Effect */ +@keyframes waterWave { + 0% { + transform: translateX(0) translateZ(0) scaleY(1); + } + 50% { + transform: translateX(-25%) translateZ(0) scaleY(0.8); + } + 100% { + transform: translateX(-50%) translateZ(0) scaleY(1); + } +} + +.water-wave { + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath d='M321.39,56.44c58-10.79,114.16-30.13,172-41.86,82.39-16.72,168.19-17.73,250.45-.39C823.78,31,906.67,72,985.66,92.83c70.05,18.48,146.53,26.09,214.34,3V0H0V27.35A600.21,600.21,0,0,0,321.39,56.44Z' fill='%23ffffff' fill-opacity='0.3'%3E%3C/path%3E%3C/svg%3E"); + background-size: 200% 100%; + animation: waterWave 10s linear infinite; +} \ No newline at end of file diff --git a/website-mockups/css/bento-layout.css b/website-mockups/css/bento-layout.css new file mode 100644 index 0000000..47cf8ea --- /dev/null +++ b/website-mockups/css/bento-layout.css @@ -0,0 +1,1230 @@ +/* Bento Grid Layout Styles */ + +:root { + --primary-color: #1e3a5f; + --accent-color: #dc2626; + --text-primary: #1e293b; + --text-secondary: #64748b; + --bg-primary: #ffffff; + --bg-secondary: #f8fafc; + --border-color: #e2e8f0; + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1); + --grid-gap: 1.5rem; + --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); + min-height: 100vh; + color: var(--text-primary); + overflow-x: hidden; +} + +/* Minimal Header */ +.minimal-header { + position: fixed; + top: 0; + left: 0; + right: 0; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + z-index: 100; + border-bottom: 1px solid var(--border-color); + transition: var(--transition); +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 2rem; + max-width: 1600px; + margin: 0 auto; +} + +.logo-section { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.logo-img { + width: 40px; + height: 40px; + border-radius: 50%; + object-fit: cover; +} + +.logo-text { + font-size: 1.25rem; + font-weight: 700; + font-family: 'Playfair Display', serif; + color: var(--primary-color); +} + +.header-actions { + display: flex; + align-items: center; + gap: 1rem; +} + +.filter-toggle { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 8px; + cursor: pointer; + transition: var(--transition); + font-size: 0.875rem; + font-weight: 500; +} + +.filter-toggle:hover { + background: var(--primary-color); + color: white; + border-color: var(--primary-color); +} + +.menu-toggle { + display: flex; + flex-direction: column; + gap: 4px; + padding: 0.5rem; + background: transparent; + border: none; + cursor: pointer; +} + +.menu-toggle span { + width: 24px; + height: 2px; + background: var(--text-primary); + transition: var(--transition); +} + +.menu-toggle:hover span { + background: var(--accent-color); +} + +/* Filter Bar */ +.filter-bar { + display: flex; + gap: 0.75rem; + padding: 0 2rem 1rem; + max-width: 1600px; + margin: 0 auto; + overflow-x: auto; + scrollbar-width: none; + opacity: 0; + max-height: 0; + transition: all 0.3s ease; +} + +.filter-bar.active { + opacity: 1; + max-height: 100px; +} + +.filter-bar::-webkit-scrollbar { + display: none; +} + +.filter-chip { + padding: 0.5rem 1rem; + background: white; + border: 1px solid var(--border-color); + border-radius: 20px; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: var(--transition); + white-space: nowrap; +} + +.filter-chip:hover { + background: var(--bg-secondary); + transform: translateY(-2px); +} + +.filter-chip.active { + background: var(--primary-color); + color: white; + border-color: var(--primary-color); +} + +/* Full Screen Menu Overlay */ +.menu-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, var(--primary-color) 0%, #0f172a 100%); + z-index: 200; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + visibility: hidden; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +.menu-overlay.active { + opacity: 1; + visibility: visible; +} + +.menu-content { + text-align: center; + color: white; +} + +.menu-close { + position: absolute; + top: 2rem; + right: 2rem; + background: transparent; + border: none; + color: white; + font-size: 3rem; + cursor: pointer; + transition: var(--transition); +} + +.menu-close:hover { + transform: rotate(90deg); +} + +.menu-nav { + display: flex; + flex-direction: column; + gap: 2rem; +} + +.menu-link { + position: relative; + color: white; + text-decoration: none; + font-size: 3rem; + font-weight: 700; + font-family: 'Playfair Display', serif; + transition: var(--transition); + overflow: hidden; +} + +.menu-link::after { + content: attr(data-text); + position: absolute; + top: 100%; + left: 0; + width: 100%; + color: var(--accent-color); + transition: var(--transition); +} + +.menu-link:hover::after { + top: 0; +} + +.menu-footer { + margin-top: 4rem; + font-size: 0.875rem; + opacity: 0.8; +} + +.menu-contact { + display: flex; + gap: 2rem; + justify-content: center; + margin-top: 1rem; +} + +.menu-contact a { + color: white; + text-decoration: none; + transition: var(--transition); +} + +.menu-contact a:hover { + color: var(--accent-color); +} + +/* Bento Grid Container */ +.bento-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + grid-auto-rows: 250px; + gap: var(--grid-gap); + padding: 120px 2rem 2rem; + max-width: 1600px; + margin: 0 auto; + animation: fadeInUp 0.6s ease; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Bento Tile Base */ +.bento-tile { + background: white; + border-radius: 20px; + overflow: hidden; + position: relative; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + cursor: pointer; + box-shadow: var(--shadow-md); + animation: tileAppear 0.6s ease backwards; +} + +.bento-tile:nth-child(odd) { + animation-delay: 0.1s; +} + +.bento-tile:nth-child(even) { + animation-delay: 0.2s; +} + +@keyframes tileAppear { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} + +.bento-tile:hover { + transform: translateY(-8px) scale(1.02); + box-shadow: var(--shadow-xl); +} + +/* Tile Sizes */ +.tile-small { + grid-column: span 1; + grid-row: span 1; +} + +.tile-medium { + grid-column: span 2; + grid-row: span 1; +} + +.tile-large { + grid-column: span 2; + grid-row: span 2; +} + +@media (max-width: 768px) { + .tile-medium, + .tile-large { + grid-column: span 1; + } + + .tile-large { + grid-row: span 2; + } +} + +/* Hero Tile */ +.tile-hero { + background: linear-gradient(135deg, var(--primary-color) 0%, #0f172a 100%); + color: white; + display: flex; + align-items: center; + padding: 3rem; +} + +.tile-hero .tile-content { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + height: 100%; +} + +.hero-text { + flex: 1; + z-index: 2; +} + +.hero-title { + font-family: 'Playfair Display', serif; + margin-bottom: 1rem; +} + +.hero-subtitle { + display: block; + font-size: 1.25rem; + font-weight: 400; + opacity: 0.9; + margin-bottom: 0.5rem; +} + +.hero-main { + display: block; + font-size: 3rem; + font-weight: 700; + line-height: 1.1; +} + +.hero-description { + font-size: 1.125rem; + opacity: 0.9; + margin-top: 1rem; +} + +.hero-visual { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 50%; + overflow: hidden; + opacity: 0.3; +} + +.hero-video, +.hero-visual img { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* Yacht Tiles */ +.tile-yacht { + padding: 0; +} + +.tile-yacht .tile-content { + height: 100%; + display: flex; + flex-direction: column; +} + +.yacht-badge { + position: absolute; + top: 1rem; + right: 1rem; + background: var(--accent-color); + color: white; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 600; + z-index: 10; +} + +.yacht-image { + position: relative; + height: 60%; + overflow: hidden; +} + +.yacht-image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: var(--transition); +} + +.tile-yacht:hover .yacht-image img { + transform: scale(1.1); +} + +.yacht-overlay { + position: absolute; + inset: 0; + background: linear-gradient(to top, rgba(0,0,0,0.7) 0%, transparent 50%); + display: flex; + align-items: flex-end; + justify-content: center; + padding: 1rem; + opacity: 0; + transition: var(--transition); +} + +.tile-yacht:hover .yacht-overlay { + opacity: 1; +} + +.quick-view-btn { + padding: 0.75rem 1.5rem; + background: white; + border: none; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.quick-view-btn:hover { + background: var(--accent-color); + color: white; +} + +.yacht-info { + padding: 1.5rem; + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.yacht-name { + font-size: 1.25rem; + font-weight: 700; + margin-bottom: 0.5rem; + font-family: 'Playfair Display', serif; +} + +.yacht-specs { + display: flex; + gap: 0.5rem; + font-size: 0.875rem; + color: var(--text-secondary); + margin-bottom: 1rem; +} + +.yacht-explore { + background: transparent; + border: 2px solid var(--primary-color); + color: var(--primary-color); + padding: 0.5rem 1rem; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.yacht-explore:hover { + background: var(--primary-color); + color: white; +} + +/* Service Tiles */ +.tile-service { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 2rem; + display: flex; + align-items: center; + justify-content: center; +} + +.tile-service .tile-content { + text-align: center; +} + +.service-icon { + font-size: 3rem; + margin-bottom: 1rem; +} + +.service-title { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.5rem; + font-family: 'Playfair Display', serif; +} + +.service-desc { + font-size: 0.875rem; + opacity: 0.9; + margin-bottom: 1rem; +} + +.service-link { + color: white; + text-decoration: none; + font-weight: 600; + border-bottom: 2px solid white; + transition: var(--transition); +} + +.service-link:hover { + border-color: transparent; +} + +/* Testimonial Tile */ +.tile-testimonial { + background: var(--bg-secondary); + padding: 1.5rem; + display: flex; + align-items: center; + justify-content: center; +} + +.tile-testimonial .tile-content { + text-align: center; +} + +.stars { + color: #fbbf24; + font-size: 1.25rem; + margin-bottom: 0.5rem; +} + +.testimonial-text { + font-size: 0.875rem; + font-style: italic; + margin-bottom: 0.5rem; + color: var(--text-primary); +} + +.testimonial-author { + font-size: 0.75rem; + font-weight: 600; + color: var(--text-secondary); +} + +/* Destination Tile */ +.tile-destination { + padding: 0; + overflow: hidden; +} + +.destination-image { + position: relative; + height: 100%; +} + +.destination-image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: var(--transition); +} + +.tile-destination:hover .destination-image img { + transform: scale(1.1); +} + +.destination-overlay { + position: absolute; + inset: 0; + background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, transparent 60%); + display: flex; + flex-direction: column; + justify-content: flex-end; + padding: 1.5rem; + color: white; +} + +.destination-name { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.25rem; + font-family: 'Playfair Display', serif; +} + +.destination-desc { + font-size: 0.875rem; + opacity: 0.9; +} + +/* Stat Tile */ +.tile-stat { + background: var(--primary-color); + color: white; + display: flex; + align-items: center; + justify-content: center; + text-align: center; +} + +.stat-number { + font-size: 2.5rem; + font-weight: 800; + margin-bottom: 0.25rem; +} + +.stat-label { + font-size: 0.875rem; + opacity: 0.9; +} + +/* Experience Tile */ +.tile-experience { + padding: 0; +} + +.tile-experience .tile-content { + display: grid; + grid-template-columns: 1fr 1fr; + height: 100%; +} + +.experience-visual { + position: relative; + overflow: hidden; +} + +.experience-visual img { + width: 100%; + height: 100%; + object-fit: cover; + transition: var(--transition); +} + +.tile-experience:hover .experience-visual img { + transform: scale(1.05); +} + +.experience-info { + padding: 2rem; + display: flex; + flex-direction: column; + justify-content: center; +} + +.experience-tag { + display: inline-block; + padding: 0.25rem 0.75rem; + background: var(--accent-color); + color: white; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 600; + margin-bottom: 1rem; + width: fit-content; +} + +.experience-title { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.75rem; + font-family: 'Playfair Display', serif; +} + +.experience-desc { + color: var(--text-secondary); + margin-bottom: 1.5rem; +} + +.experience-btn { + padding: 0.75rem 1.5rem; + background: var(--primary-color); + color: white; + border: none; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.experience-btn:hover { + background: var(--accent-color); +} + +/* Service Mini Tile */ +.tile-service-mini { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); + color: white; + padding: 1.5rem; + display: flex; + align-items: center; + justify-content: center; +} + +.tile-service-mini .tile-content { + text-align: center; +} + +.service-mini-icon { + font-size: 2rem; + margin-bottom: 0.5rem; +} + +.tile-service-mini h4 { + font-size: 1rem; + margin-bottom: 0.5rem; +} + +.mini-link { + color: white; + text-decoration: none; + font-size: 1.5rem; + transition: var(--transition); +} + +.mini-link:hover { + transform: translateX(5px); +} + +/* Video Tile */ +.tile-video { + padding: 0; + position: relative; + overflow: hidden; +} + +.tile-video video { + width: 100%; + height: 100%; + object-fit: cover; +} + +.video-overlay { + position: absolute; + inset: 0; + background: rgba(0,0,0,0.4); + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: var(--transition); +} + +.tile-video:hover .video-overlay { + opacity: 1; +} + +.play-btn { + padding: 1rem 2rem; + background: white; + border: none; + border-radius: 50px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.play-btn:hover { + background: var(--accent-color); + color: white; + transform: scale(1.1); +} + +/* CTA Tile */ +.tile-cta { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 2rem; + display: flex; + align-items: center; + justify-content: center; +} + +.tile-cta .tile-content { + text-align: center; +} + +.cta-title { + font-size: 1.75rem; + font-weight: 700; + margin-bottom: 0.5rem; + font-family: 'Playfair Display', serif; +} + +.cta-desc { + margin-bottom: 1.5rem; + opacity: 0.9; +} + +.cta-buttons { + display: flex; + gap: 1rem; + justify-content: center; +} + +.btn-primary, +.btn-secondary { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.btn-primary { + background: white; + color: var(--primary-color); +} + +.btn-primary:hover { + background: var(--accent-color); + color: white; +} + +.btn-secondary { + background: transparent; + color: white; + border: 2px solid white; +} + +.btn-secondary:hover { + background: white; + color: var(--primary-color); +} + +/* Social Tile */ +.tile-social { + background: linear-gradient(135deg, #e1306c 0%, #fd1d1d 50%, #f77737 100%); + color: white; + padding: 1.5rem; + display: flex; + align-items: center; + justify-content: center; +} + +.tile-social .tile-content { + text-align: center; +} + +.social-icon { + margin-bottom: 0.5rem; +} + +.social-handle { + font-weight: 600; + margin-bottom: 0.5rem; +} + +.social-link { + color: white; + text-decoration: none; + font-weight: 600; + border-bottom: 2px solid white; +} + +/* Weather Tile */ +.tile-weather { + background: linear-gradient(135deg, #89f7fe 0%, #66a6ff 100%); + color: white; + padding: 1.5rem; + display: flex; + align-items: center; + justify-content: center; +} + +.tile-weather .tile-content { + text-align: center; +} + +.weather-icon { + font-size: 2rem; + margin-bottom: 0.5rem; +} + +.weather-temp { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.25rem; +} + +.weather-desc { + font-size: 0.875rem; + opacity: 0.9; +} + +/* Offer Tile */ +.tile-offer { + background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); + color: var(--text-primary); + padding: 2rem; + display: flex; + align-items: center; + justify-content: center; +} + +.offer-badge { + position: absolute; + top: 1rem; + right: 1rem; + background: var(--accent-color); + color: white; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 600; +} + +.offer-title { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.5rem; + font-family: 'Playfair Display', serif; +} + +.offer-desc { + margin-bottom: 1rem; + color: var(--text-secondary); +} + +.offer-btn { + padding: 0.75rem 1.5rem; + background: white; + color: var(--primary-color); + border: none; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.offer-btn:hover { + background: var(--primary-color); + color: white; +} + +/* Floating Action Button */ +.fab-container { + position: fixed; + bottom: 2rem; + right: 2rem; + z-index: 90; +} + +.fab { + width: 60px; + height: 60px; + border-radius: 50%; + background: var(--accent-color); + color: white; + border: none; + box-shadow: var(--shadow-lg); + cursor: pointer; + transition: var(--transition); + display: flex; + align-items: center; + justify-content: center; +} + +.fab:hover { + transform: scale(1.1) rotate(45deg); + background: var(--primary-color); +} + +.fab-menu { + position: absolute; + bottom: 70px; + right: 0; + display: flex; + flex-direction: column; + gap: 0.5rem; + opacity: 0; + visibility: hidden; + transition: var(--transition); +} + +.fab-menu.active { + opacity: 1; + visibility: visible; +} + +.fab-option { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1rem; + background: white; + border: 1px solid var(--border-color); + border-radius: 50px; + box-shadow: var(--shadow-md); + cursor: pointer; + transition: var(--transition); + white-space: nowrap; +} + +.fab-option:hover { + background: var(--primary-color); + color: white; + transform: translateX(-10px); +} + +/* Wishlist Sidebar */ +.wishlist-sidebar { + position: fixed; + top: 0; + right: -400px; + width: 400px; + height: 100vh; + background: white; + box-shadow: var(--shadow-xl); + z-index: 150; + transition: var(--transition); + display: flex; + flex-direction: column; +} + +.wishlist-sidebar.active { + right: 0; +} + +.wishlist-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1.5rem; + border-bottom: 1px solid var(--border-color); +} + +.wishlist-close { + background: transparent; + border: none; + font-size: 2rem; + cursor: pointer; + transition: var(--transition); +} + +.wishlist-close:hover { + transform: rotate(90deg); +} + +.wishlist-content { + flex: 1; + padding: 1.5rem; + overflow-y: auto; +} + +.wishlist-empty { + text-align: center; + color: var(--text-secondary); + padding: 2rem; +} + +.wishlist-footer { + padding: 1.5rem; + border-top: 1px solid var(--border-color); +} + +.wishlist-inquire { + width: 100%; + padding: 1rem; + background: var(--primary-color); + color: white; + border: none; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.wishlist-inquire:hover { + background: var(--accent-color); +} + +/* Layout Switcher */ +.layout-switcher { + position: fixed; + bottom: 2rem; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 0.5rem; + background: white; + padding: 0.5rem; + border-radius: 50px; + box-shadow: var(--shadow-lg); + z-index: 90; +} + +.layout-btn { + padding: 0.5rem 1rem; + background: transparent; + border: none; + border-radius: 50px; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: var(--transition); +} + +.layout-btn:hover { + background: var(--bg-secondary); +} + +.layout-btn.active { + background: var(--primary-color); + color: white; +} + +/* Filter Animations */ +.bento-tile.hiding { + opacity: 0; + transform: scale(0.8); + pointer-events: none; +} + +.bento-tile.showing { + animation: showTile 0.5s ease forwards; +} + +@keyframes showTile { + from { + opacity: 0; + transform: scale(0.8); + } + to { + opacity: 1; + transform: scale(1); + } +} + +/* Responsive Design */ +@media (max-width: 1200px) { + .bento-container { + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + grid-auto-rows: 200px; + } +} + +@media (max-width: 768px) { + .bento-container { + grid-template-columns: 1fr; + padding: 100px 1rem 1rem; + } + + .tile-medium { + grid-column: span 1; + } + + .tile-large { + grid-column: span 1; + grid-row: span 2; + } + + .hero-main { + font-size: 2rem; + } + + .tile-experience .tile-content { + grid-template-columns: 1fr; + } + + .wishlist-sidebar { + width: 100%; + right: -100%; + } + + .layout-switcher { + flex-direction: column; + left: 1rem; + transform: none; + bottom: 5rem; + } +} \ No newline at end of file diff --git a/website-mockups/css/booking.css b/website-mockups/css/booking.css new file mode 100644 index 0000000..62c1bd9 --- /dev/null +++ b/website-mockups/css/booking.css @@ -0,0 +1,586 @@ +/* HarborSmith - Booking Styles */ +/* ============================= */ + +/* Progress Bar */ +.booking-progress { + padding: 100px 0 40px; + background: var(--surface); + border-bottom: 1px solid var(--border); +} + +.progress-steps { + display: flex; + align-items: center; + justify-content: center; + max-width: 600px; + margin: 0 auto; +} + +.progress-step { + display: flex; + flex-direction: column; + align-items: center; + position: relative; +} + +.step-number { + width: 40px; + height: 40px; + border-radius: 50%; + background: var(--background); + border: 2px solid var(--border); + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + color: var(--text-secondary); + transition: all var(--transition-base); +} + +.progress-step.active .step-number { + background: var(--primary); + color: white; + border-color: var(--primary); +} + +.progress-step.completed .step-number { + background: var(--accent); + color: white; + border-color: var(--accent); +} + +.step-label { + margin-top: 8px; + font-size: var(--text-sm); + color: var(--text-secondary); + white-space: nowrap; +} + +.progress-step.active .step-label { + color: var(--primary); + font-weight: 600; +} + +.progress-line { + width: 80px; + height: 2px; + background: var(--border); + margin: 0 10px; + position: relative; + top: -20px; +} + +.progress-step.completed + .progress-line { + background: var(--accent); +} + +/* Booking Section */ +.booking-section { + padding: 60px 0; + min-height: calc(100vh - 200px); +} + +.booking-header { + text-align: center; + margin-bottom: 40px; +} + +.booking-header h1 { + font-size: var(--text-4xl); + margin-bottom: 10px; +} + +.booking-header p { + color: var(--text-secondary); + font-size: var(--text-lg); +} + +.booking-content { + display: grid; + grid-template-columns: 1fr 350px; + gap: 40px; +} + +.booking-main { + display: flex; + flex-direction: column; + gap: 30px; +} + +.booking-card { + background: var(--background); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + padding: 30px; + box-shadow: var(--shadow-sm); +} + +.booking-card h3 { + font-size: var(--text-xl); + margin-bottom: 20px; +} + +/* Calendar */ +.calendar-container { + background: var(--surface); + border-radius: var(--radius-lg); + padding: 20px; +} + +.calendar-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.calendar-nav { + width: 32px; + height: 32px; + border-radius: var(--radius-md); + border: 1px solid var(--border); + background: var(--background); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + transition: all var(--transition-fast); +} + +.calendar-nav:hover { + background: var(--primary); + color: white; + border-color: var(--primary); +} + +.calendar-month { + font-size: var(--text-lg); + font-weight: 600; +} + +.calendar-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 5px; +} + +.calendar-day-header { + text-align: center; + font-size: var(--text-sm); + font-weight: 600; + color: var(--text-secondary); + padding: 10px 0; +} + +.calendar-day { + aspect-ratio: 1; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-md); + cursor: pointer; + font-weight: 500; + transition: all var(--transition-fast); +} + +.calendar-day.other-month { + color: var(--text-secondary); + opacity: 0.3; +} + +.calendar-day.available { + background: var(--background); + color: var(--text); +} + +.calendar-day.available:hover { + background: var(--primary); + color: white; +} + +.calendar-day.unavailable { + background: var(--surface); + color: var(--text-secondary); + cursor: not-allowed; + text-decoration: line-through; +} + +.calendar-day.selected { + background: var(--primary); + color: white; +} + +.calendar-legend { + display: flex; + gap: 20px; + margin-top: 20px; + font-size: var(--text-sm); +} + +.legend-dot { + display: inline-block; + width: 12px; + height: 12px; + border-radius: 50%; + margin-right: 5px; +} + +.legend-dot.available { + background: var(--background); + border: 2px solid var(--primary); +} + +.legend-dot.unavailable { + background: var(--text-secondary); +} + +.legend-dot.selected { + background: var(--primary); +} + +/* Form Elements */ +.form-group { + margin-bottom: 25px; +} + +.form-group label { + display: block; + margin-bottom: 8px; + font-weight: 500; + color: var(--text); +} + +.form-control { + width: 100%; + padding: 12px 15px; + border: 1px solid var(--border); + border-radius: var(--radius-md); + font-size: var(--text-base); + font-family: inherit; + transition: all var(--transition-fast); + background: var(--background); +} + +.form-control:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.1); +} + +.form-text { + display: block; + margin-top: 5px; + font-size: var(--text-sm); + color: var(--text-secondary); +} + +/* Duration Options */ +.duration-options { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 15px; +} + +.duration-option input { + display: none; +} + +.duration-card { + display: flex; + flex-direction: column; + padding: 20px; + border: 2px solid var(--border); + border-radius: var(--radius-lg); + cursor: pointer; + transition: all var(--transition-base); + position: relative; +} + +.duration-option input:checked + .duration-card { + border-color: var(--primary); + background: var(--surface); +} + +.duration-card:hover { + border-color: var(--primary); + transform: translateY(-2px); +} + +.duration-time { + font-size: var(--text-lg); + font-weight: 600; + margin-bottom: 5px; +} + +.duration-price { + color: var(--primary); + font-size: var(--text-xl); + font-weight: 700; +} + +.duration-badge { + position: absolute; + top: 10px; + right: 10px; + padding: 4px 8px; + background: var(--accent); + color: white; + font-size: var(--text-xs); + border-radius: var(--radius-full); + font-weight: 600; +} + +/* Booking Sidebar */ +.booking-sidebar { + display: flex; + flex-direction: column; + gap: 30px; +} + +.booking-summary { + background: var(--surface); + border-radius: var(--radius-xl); + padding: 30px; + position: sticky; + top: 100px; +} + +.booking-summary h3 { + font-size: var(--text-xl); + margin-bottom: 20px; +} + +.summary-item { + display: flex; + justify-content: space-between; + margin-bottom: 15px; + font-size: var(--text-base); +} + +.summary-item span { + color: var(--text-secondary); +} + +.summary-item strong { + color: var(--text); +} + +.summary-divider { + height: 1px; + background: var(--border); + margin: 20px 0; +} + +.summary-total { + font-size: var(--text-lg); +} + +.summary-total strong { + color: var(--primary); + font-size: var(--text-2xl); +} + +.summary-note { + font-size: var(--text-sm); + color: var(--text-secondary); + font-style: italic; + margin-top: 15px; +} + +.booking-help { + background: var(--background); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 20px; + text-align: center; +} + +.booking-help h4 { + font-size: var(--text-lg); + margin-bottom: 10px; +} + +.booking-help p { + color: var(--text-secondary); + margin-bottom: 15px; + font-size: var(--text-sm); +} + +/* Navigation */ +.booking-navigation { + display: flex; + justify-content: space-between; + margin-top: 40px; + padding-top: 30px; + border-top: 1px solid var(--border); +} + +/* Yacht Selection Grid */ +.yacht-selection-grid { + display: grid; + gap: 30px; +} + +.yacht-select-card { + display: grid; + grid-template-columns: 300px 1fr; + background: var(--background); + border: 2px solid var(--border); + border-radius: var(--radius-xl); + overflow: hidden; + transition: all var(--transition-base); + cursor: pointer; +} + +.yacht-select-card:hover { + border-color: var(--primary); + box-shadow: var(--shadow-lg); +} + +.yacht-select-card.selected { + border-color: var(--primary); + background: var(--surface); +} + +.yacht-select-image { + height: 200px; + background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: var(--text-xl); +} + +.yacht-select-info { + padding: 30px; +} + +.yacht-select-header { + display: flex; + justify-content: space-between; + align-items: start; + margin-bottom: 15px; +} + +.yacht-select-name { + font-size: var(--text-2xl); + font-weight: 700; + margin-bottom: 5px; +} + +.yacht-select-type { + color: var(--text-secondary); +} + +.yacht-select-price { + text-align: right; +} + +.yacht-select-specs { + display: flex; + gap: 30px; + margin: 20px 0; +} + +.spec-item { + display: flex; + align-items: center; + gap: 8px; + color: var(--text-secondary); + font-size: var(--text-sm); +} + +/* Customization Options */ +.customization-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; +} + +.addon-card { + border: 2px solid var(--border); + border-radius: var(--radius-lg); + padding: 20px; + transition: all var(--transition-base); + cursor: pointer; +} + +.addon-card:hover { + border-color: var(--primary); +} + +.addon-card.selected { + border-color: var(--primary); + background: var(--surface); +} + +.addon-header { + display: flex; + justify-content: space-between; + align-items: start; + margin-bottom: 10px; +} + +.addon-name { + font-weight: 600; +} + +.addon-price { + color: var(--primary); + font-weight: 700; +} + +.addon-description { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +/* Responsive */ +@media (max-width: 1024px) { + .booking-content { + grid-template-columns: 1fr; + } + + .booking-sidebar { + order: -1; + } + + .booking-summary { + position: static; + } +} + +@media (max-width: 768px) { + .progress-steps { + overflow-x: auto; + padding: 0 20px; + } + + .progress-line { + width: 40px; + } + + .step-label { + font-size: var(--text-xs); + } + + .duration-options { + grid-template-columns: 1fr; + } + + .yacht-select-card { + grid-template-columns: 1fr; + } + + .booking-navigation { + flex-direction: column; + gap: 15px; + } + + .booking-navigation .btn { + width: 100%; + justify-content: center; + } +} \ No newline at end of file diff --git a/website-mockups/css/maintenance-enhanced.css b/website-mockups/css/maintenance-enhanced.css new file mode 100644 index 0000000..ff650ef --- /dev/null +++ b/website-mockups/css/maintenance-enhanced.css @@ -0,0 +1,747 @@ +/* Enhanced Maintenance Portal Styles */ +/* ================================== */ + +:root { + /* Modern Color Palette */ + --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + --secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); + --success-gradient: linear-gradient(135deg, #0ba360 0%, #3cba92 100%); + --warning-gradient: linear-gradient(135deg, #f2994a 0%, #f2c94c 100%); + --danger-gradient: linear-gradient(135deg, #eb3349 0%, #f45c43 100%); + + /* Enhanced shadows */ + --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.06); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08); + --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12); + --shadow-xl: 0 12px 48px rgba(0, 0, 0, 0.16); + --shadow-glow: 0 0 20px rgba(102, 126, 234, 0.4); + + /* Spacing */ + --spacing-xs: 0.25rem; + --spacing-sm: 0.5rem; + --spacing-md: 1rem; + --spacing-lg: 1.5rem; + --spacing-xl: 2rem; + --spacing-2xl: 3rem; + + /* Border radius */ + --radius-sm: 6px; + --radius-md: 12px; + --radius-lg: 16px; + --radius-xl: 20px; + --radius-full: 9999px; + + /* Transitions */ + --transition-fast: 0.15s ease; + --transition-base: 0.3s ease; + --transition-slow: 0.5s ease; +} + +/* Global Enhancements */ +body { + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); + min-height: 100vh; +} + +/* Enhanced Dashboard Cards */ +.dashboard-card, .card, .portal-card { + background: white; + border-radius: var(--radius-lg); + padding: var(--spacing-lg); + box-shadow: var(--shadow-md); + border: 1px solid rgba(255, 255, 255, 0.8); + transition: all var(--transition-base); + position: relative; + overflow: hidden; +} + +.dashboard-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: var(--primary-gradient); + opacity: 0; + transition: opacity var(--transition-base); +} + +.dashboard-card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-xl); +} + +.dashboard-card:hover::before { + opacity: 1; +} + +/* Enhanced Stats Cards */ +.stat-card { + background: white; + border-radius: var(--radius-lg); + padding: var(--spacing-lg); + position: relative; + overflow: hidden; + transition: all var(--transition-base); + border: 1px solid #e5e7eb; +} + +.stat-card::after { + content: ''; + position: absolute; + top: -50%; + right: -50%; + width: 200%; + height: 200%; + background: var(--primary-gradient); + opacity: 0.05; + border-radius: 50%; + transition: all var(--transition-slow); +} + +.stat-card:hover::after { + top: -75%; + right: -75%; + opacity: 0.1; +} + +.stat-card:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); + border-color: #667eea; +} + +.stat-value { + font-size: 2.5rem; + font-weight: 700; + background: var(--primary-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin: var(--spacing-sm) 0; +} + +.stat-label { + color: #6b7280; + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.05em; + font-weight: 600; +} + +.stat-change { + display: inline-flex; + align-items: center; + gap: var(--spacing-xs); + padding: var(--spacing-xs) var(--spacing-sm); + border-radius: var(--radius-full); + font-size: 0.875rem; + font-weight: 600; + margin-top: var(--spacing-sm); +} + +.stat-change.positive { + background: #dcfce7; + color: #16a34a; +} + +.stat-change.negative { + background: #fee2e2; + color: #dc2626; +} + +/* Enhanced Tables */ +.table-container { + background: white; + border-radius: var(--radius-lg); + overflow: hidden; + box-shadow: var(--shadow-md); +} + +table { + width: 100%; + border-collapse: separate; + border-spacing: 0; +} + +thead { + background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%); +} + +thead th { + padding: var(--spacing-md) var(--spacing-lg); + text-align: left; + font-weight: 600; + color: #374151; + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.05em; + border-bottom: 2px solid #e5e7eb; +} + +tbody tr { + transition: all var(--transition-fast); + border-bottom: 1px solid #f3f4f6; +} + +tbody tr:hover { + background: linear-gradient(90deg, rgba(102, 126, 234, 0.05) 0%, rgba(118, 75, 162, 0.05) 100%); +} + +tbody td { + padding: var(--spacing-md) var(--spacing-lg); + color: #4b5563; +} + +/* Enhanced Status Badges */ +.status-badge, .badge { + display: inline-flex; + align-items: center; + gap: var(--spacing-xs); + padding: var(--spacing-xs) var(--spacing-sm); + border-radius: var(--radius-full); + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + transition: all var(--transition-fast); +} + +.status-badge.active, .badge.success { + background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%); + color: #065f46; +} + +.status-badge.scheduled, .badge.warning { + background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); + color: #92400e; +} + +.status-badge.overdue, .badge.danger { + background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%); + color: #dc2626; +} + +.status-badge.completed, .badge.info { + background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); + color: #1e40af; +} + +/* Enhanced Buttons */ +.btn, .portal-btn, button[type="submit"] { + padding: var(--spacing-sm) var(--spacing-lg); + border-radius: var(--radius-md); + font-weight: 600; + transition: all var(--transition-base); + cursor: pointer; + border: none; + display: inline-flex; + align-items: center; + gap: var(--spacing-sm); + position: relative; + overflow: hidden; +} + +.btn::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.3); + transform: translate(-50%, -50%); + transition: width var(--transition-base), height var(--transition-base); +} + +.btn:hover::before { + width: 300px; + height: 300px; +} + +.btn-primary { + background: var(--primary-gradient); + color: white; + box-shadow: 0 4px 6px rgba(102, 126, 234, 0.25); +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.35); +} + +.btn-secondary { + background: white; + color: #4b5563; + border: 2px solid #e5e7eb; +} + +.btn-secondary:hover { + background: #f9fafb; + border-color: #667eea; + color: #667eea; + transform: translateY(-2px); +} + +.btn-success { + background: var(--success-gradient); + color: white; + box-shadow: 0 4px 6px rgba(16, 185, 129, 0.25); +} + +.btn-danger { + background: var(--danger-gradient); + color: white; + box-shadow: 0 4px 6px rgba(239, 68, 68, 0.25); +} + +/* Enhanced Form Elements */ +.form-group { + margin-bottom: var(--spacing-lg); +} + +.form-label { + display: block; + margin-bottom: var(--spacing-sm); + font-weight: 600; + color: #374151; + font-size: 0.875rem; + letter-spacing: 0.025em; +} + +.form-input, .form-select, .form-textarea, +input[type="text"], input[type="email"], input[type="number"], +input[type="date"], input[type="time"], select, textarea { + width: 100%; + padding: var(--spacing-sm) var(--spacing-md); + border: 2px solid #e5e7eb; + border-radius: var(--radius-md); + background: #f9fafb; + transition: all var(--transition-base); + font-family: inherit; +} + +.form-input:focus, .form-select:focus, .form-textarea:focus, +input:focus, select:focus, textarea:focus { + outline: none; + border-color: #667eea; + background: white; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); + transform: translateY(-1px); +} + +/* Enhanced Sidebar */ +.portal-sidebar { + background: linear-gradient(180deg, #1f2937 0%, #111827 100%); + box-shadow: 4px 0 24px rgba(0, 0, 0, 0.1); +} + +.sidebar-nav-item { + margin: var(--spacing-xs) var(--spacing-md); + border-radius: var(--radius-md); + transition: all var(--transition-base); +} + +.sidebar-nav-item a { + display: flex; + align-items: center; + gap: var(--spacing-md); + padding: var(--spacing-sm) var(--spacing-md); + color: #9ca3af; + text-decoration: none; + transition: all var(--transition-base); + position: relative; +} + +.sidebar-nav-item a::before { + content: ''; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 3px; + height: 0; + background: var(--primary-gradient); + transition: height var(--transition-base); + border-radius: 0 3px 3px 0; +} + +.sidebar-nav-item:hover { + background: rgba(255, 255, 255, 0.05); +} + +.sidebar-nav-item:hover a { + color: white; +} + +.sidebar-nav-item.active { + background: rgba(102, 126, 234, 0.1); +} + +.sidebar-nav-item.active a { + color: white; +} + +.sidebar-nav-item.active a::before { + height: 24px; +} + +/* Enhanced Header */ +.portal-header { + background: white; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + border-bottom: 1px solid #e5e7eb; +} + +.header-search { + position: relative; +} + +.header-search input { + padding-left: 2.5rem; + background: #f3f4f6; + border: 2px solid transparent; + border-radius: var(--radius-full); +} + +.header-search input:focus { + background: white; + border-color: #667eea; +} + +.header-search i { + position: absolute; + left: var(--spacing-md); + top: 50%; + transform: translateY(-50%); + color: #9ca3af; +} + +/* Enhanced Charts Container */ +.chart-container { + background: white; + border-radius: var(--radius-lg); + padding: var(--spacing-lg); + box-shadow: var(--shadow-md); + position: relative; +} + +.chart-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--spacing-lg); + padding-bottom: var(--spacing-md); + border-bottom: 2px solid #f3f4f6; +} + +.chart-title { + font-size: 1.125rem; + font-weight: 700; + color: #1f2937; +} + +/* Enhanced Progress Bars */ +.progress-bar { + width: 100%; + height: 8px; + background: #e5e7eb; + border-radius: var(--radius-full); + overflow: hidden; + position: relative; +} + +.progress-bar-fill { + height: 100%; + background: var(--primary-gradient); + border-radius: var(--radius-full); + transition: width var(--transition-slow); + position: relative; + overflow: hidden; +} + +.progress-bar-fill::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.3), + transparent + ); + animation: shimmer 2s infinite; +} + +@keyframes shimmer { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(100%); } +} + +/* Enhanced Notifications */ +.notification { + padding: var(--spacing-md); + border-radius: var(--radius-md); + margin-bottom: var(--spacing-md); + display: flex; + align-items: flex-start; + gap: var(--spacing-md); + animation: slideIn var(--transition-base); +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateX(-20px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.notification.info { + background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%); + border-left: 4px solid #3b82f6; +} + +.notification.success { + background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%); + border-left: 4px solid #22c55e; +} + +.notification.warning { + background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); + border-left: 4px solid #f59e0b; +} + +.notification.error { + background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%); + border-left: 4px solid #ef4444; +} + +/* Enhanced Modals */ +.modal-overlay { + background: rgba(0, 0, 0, 0.5); + -webkit-backdrop-filter: blur(5px); + backdrop-filter: blur(5px); +} + +.modal-content { + background: white; + border-radius: var(--radius-xl); + box-shadow: var(--shadow-xl); + animation: modalSlideIn var(--transition-base); +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: translateY(-30px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +/* Enhanced Timeline */ +.timeline-item { + position: relative; + padding-left: 3rem; + padding-bottom: 2rem; +} + +.timeline-item::before { + content: ''; + position: absolute; + left: 1rem; + top: 0.5rem; + bottom: -2rem; + width: 2px; + background: linear-gradient(180deg, #667eea 0%, #764ba2 100%); +} + +.timeline-item:last-child::before { + display: none; +} + +.timeline-dot { + position: absolute; + left: 0.5rem; + top: 0.5rem; + width: 1rem; + height: 1rem; + background: white; + border: 3px solid #667eea; + border-radius: 50%; + box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1); +} + +/* Enhanced Tooltips */ +.tooltip { + position: relative; +} + +.tooltip::after { + content: attr(data-tooltip); + position: absolute; + bottom: 125%; + left: 50%; + transform: translateX(-50%); + padding: var(--spacing-sm) var(--spacing-md); + background: #1f2937; + color: white; + font-size: 0.875rem; + border-radius: var(--radius-md); + white-space: nowrap; + opacity: 0; + pointer-events: none; + transition: opacity var(--transition-base); + box-shadow: var(--shadow-lg); +} + +.tooltip:hover::after { + opacity: 1; +} + +/* Enhanced Loading States */ +.skeleton { + background: linear-gradient( + 90deg, + #f3f4f6 25%, + #e5e7eb 50%, + #f3f4f6 75% + ); + background-size: 200% 100%; + animation: loading 1.5s infinite; + border-radius: var(--radius-md); +} + +@keyframes loading { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} + +.spinner { + width: 40px; + height: 40px; + border: 4px solid #e5e7eb; + border-top-color: #667eea; + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* Smooth Scrollbar */ +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-track { + background: var(--gray-100); +} + +::-webkit-scrollbar-thumb { + background: var(--gray-400); + border-radius: var(--radius-full); +} + +::-webkit-scrollbar-thumb:hover { + background: var(--gray-500); +} + +/* Focus Visible Enhancement */ +:focus-visible { + outline: 2px solid var(--primary-orange); + outline-offset: 2px; +} + +/* Selection Color */ +::selection { + background: rgba(220, 20, 60, 0.2); + color: var(--gray-900); +} + +/* Responsive Adjustments */ +@media (max-width: 768px) { + .dashboard-grid { + grid-template-columns: 1fr; + } + + .portal-content { + padding: var(--space-md); + } + + table { + font-size: var(--text-sm); + } + + .table-wrapper { + overflow-x: auto; + } +} + +/* Print Styles */ +@media print { + .portal-sidebar, + .portal-header, + .btn, + .no-print { + display: none !important; + } + + .portal-main { + margin-left: 0 !important; + } + + .portal-content { + margin: 0; + padding: 0; + } + + .dashboard-card, + .card { + box-shadow: none; + border: 1px solid var(--gray-300); + page-break-inside: avoid; + } +} + +/* Accessibility Improvements */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* Skip to content link */ +.skip-to-content { + position: absolute; + top: -40px; + left: 0; + background: var(--primary-blue); + color: var(--white); + padding: var(--space-sm) var(--space-md); + text-decoration: none; + z-index: 9999; +} + +.skip-to-content:focus { + top: 0; +} diff --git a/website-mockups/css/portal-styles.css b/website-mockups/css/portal-styles.css new file mode 100644 index 0000000..93b45b8 --- /dev/null +++ b/website-mockups/css/portal-styles.css @@ -0,0 +1,1456 @@ +/* HarborSmith Client Portal Styles */ +/* ================================ */ + +/* CSS Reset and Base Styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* Root Variables */ +:root { + /* Colors */ + --primary-50: #eff6ff; + --primary-100: #dbeafe; + --primary-200: #bfdbfe; + --primary-500: #3b82f6; + --primary-600: #2563eb; + --primary-700: #1d4ed8; + --primary-900: #1e3a8a; + + --gray-50: #f9fafb; + --gray-100: #f3f4f6; + --gray-200: #e5e7eb; + --gray-300: #d1d5db; + --gray-400: #9ca3af; + --gray-500: #6b7280; + --gray-600: #4b5563; + --gray-700: #374151; + --gray-800: #1f2937; + --gray-900: #111827; + + --success-50: #f0fdf4; + --success-100: #dcfce7; + --success-500: #22c55e; + --success-600: #16a34a; + --success-700: #15803d; + + --warning-50: #fffbeb; + --warning-100: #fef3c7; + --warning-500: #f59e0b; + --warning-600: #d97706; + --warning-700: #b45309; + + --danger-50: #fef2f2; + --danger-100: #fee2e2; + --danger-200: #fecaca; + --danger-500: #ef4444; + --danger-600: #dc2626; + --danger-700: #b91c1c; + + --info-50: #eff6ff; + --info-100: #dbeafe; + --info-200: #bfdbfe; + --info-500: #3b82f6; + --info-600: #2563eb; + --info-700: #1d4ed8; + + /* Shadows */ + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1); + + /* Layout */ + --sidebar-width: 260px; + --header-height: 70px; +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + line-height: 1.6; + color: var(--gray-800); + background: var(--gray-50); +} + +/* Portal-specific variables */ +.portal { + --sidebar-width: 280px; + --sidebar-collapsed: 80px; + --header-height: 70px; + --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + --card-shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.12); + --success: #10b981; + --warning: #f59e0b; + --error: #ef4444; + --info: #3b82f6; +} + +/* Portal Layout Structure */ +.portal-wrapper { + min-height: 100vh; + background: var(--soft-cream); + display: flex; +} + +/* Sidebar Navigation */ +.portal-sidebar { + width: var(--sidebar-width); + background: var(--primary-blue); + color: var(--white); + position: fixed; + height: 100vh; + left: 0; + top: 0; + transition: var(--transition); + z-index: 1000; + overflow-y: auto; +} + +.portal-sidebar.collapsed { + width: var(--sidebar-collapsed); +} + +.sidebar-header { + padding: var(--space-md); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + align-items: center; + justify-content: space-between; +} + +.sidebar-logo { + display: flex; + align-items: center; + gap: var(--space-sm); + text-decoration: none; + color: var(--white); +} + +.sidebar-logo img { + width: 40px; + height: 40px; + border-radius: 8px; +} + +.sidebar-logo span { + font-family: var(--font-display); + font-size: 1.25rem; + font-weight: 700; + transition: opacity 0.3s; +} + +.portal-sidebar.collapsed .sidebar-logo span { + opacity: 0; + width: 0; +} + +.sidebar-toggle { + background: none; + border: none; + color: var(--white); + cursor: pointer; + padding: 0.5rem; + border-radius: 4px; + transition: var(--transition); +} + +.sidebar-toggle:hover { + background: rgba(255, 255, 255, 0.1); +} + +/* Sidebar Navigation Menu */ +.sidebar-nav { + padding: var(--space-md) 0; +} + +.sidebar-section { + margin-bottom: var(--space-md); +} + +.sidebar-section-title { + padding: 0 var(--space-md); + margin-bottom: var(--space-sm); + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.1em; + opacity: 0.7; + transition: opacity 0.3s; +} + +.portal-sidebar.collapsed .sidebar-section-title { + opacity: 0; +} + +.sidebar-link { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: 0.75rem var(--space-md); + color: var(--white); + text-decoration: none; + transition: var(--transition); + position: relative; +} + +.sidebar-link:hover { + background: rgba(255, 255, 255, 0.1); +} + +.sidebar-link.active { + background: var(--warm-orange); +} + +.sidebar-link.active::before { + content: ''; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 4px; + background: var(--warm-yellow); +} + +.sidebar-link i { + width: 24px; + height: 24px; + flex-shrink: 0; +} + +.sidebar-link span { + transition: opacity 0.3s; +} + +.portal-sidebar.collapsed .sidebar-link span { + opacity: 0; + width: 0; +} + +/* Main Content Area */ +.portal-main { + margin-left: var(--sidebar-width); + flex: 1; + transition: var(--transition); + min-height: 100vh; +} + +.portal-sidebar.collapsed + .portal-main { + margin-left: var(--sidebar-collapsed); +} + +/* Portal Header */ +.portal-header { + background: var(--white); + height: var(--header-height); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 var(--space-lg); + border-bottom: 1px solid var(--border); + position: sticky; + top: 0; + z-index: 100; +} + +.portal-breadcrumb { + display: flex; + align-items: center; + gap: var(--space-xs); + color: var(--text-light); +} + +.breadcrumb-separator { + margin: 0 var(--space-xs); +} + +.portal-header-actions { + display: flex; + align-items: center; + gap: var(--space-md); +} + +.header-search { + position: relative; +} + +.header-search input { + padding: 0.5rem 1rem 0.5rem 2.5rem; + border: 1px solid var(--border); + border-radius: 8px; + width: 300px; + transition: var(--transition); +} + +.header-search input:focus { + outline: none; + border-color: var(--warm-orange); + box-shadow: 0 0 0 3px rgba(220, 20, 60, 0.1); +} + +.header-search i { + position: absolute; + left: 0.75rem; + top: 50%; + transform: translateY(-50%); + color: var(--text-light); + width: 20px; + height: 20px; +} + +.header-notifications { + position: relative; +} + +.notification-badge { + position: absolute; + top: -4px; + right: -4px; + background: var(--warm-orange); + color: var(--white); + font-size: 0.75rem; + padding: 2px 6px; + border-radius: 10px; +} + +.header-user { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: 0.5rem; + border-radius: 8px; + cursor: pointer; + transition: var(--transition); +} + +.header-user:hover { + background: var(--soft-cream); +} + +.user-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + background: var(--gradient-warm); + display: flex; + align-items: center; + justify-content: center; + color: var(--white); + font-weight: 600; +} + +/* Portal Content */ +.portal-content { + padding: 2rem; + max-width: 1400px; + margin: 0 auto; +} + +.portal-page-header { + margin-bottom: var(--space-lg); +} + +.portal-page-title { + font-family: var(--font-display); + font-size: 2rem; + color: var(--text-dark); + margin-bottom: 0.5rem; +} + +.portal-page-subtitle { + color: var(--text-light); + font-size: 1.1rem; +} + +/* Dashboard Cards */ +.dashboard-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--space-md); + margin-bottom: var(--space-lg); +} + +/* Portal Container */ +.portal-container { + display: flex; + min-height: 100vh; + background: var(--gray-50); +} + +/* Main Content Wrapper */ +.portal-main { + flex: 1; + margin-left: var(--sidebar-width); + transition: margin-left 0.3s ease; +} + +/* Portal Sidebar Styles */ +.portal-sidebar { + width: var(--sidebar-width); + background: #1e3a5f; + position: fixed; + height: 100vh; + left: 0; + top: 0; + z-index: 100; + display: flex; + flex-direction: column; +} + +.sidebar-header { + padding: 1.5rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.sidebar-logo { + height: 40px; + width: auto; +} + +.sidebar-nav { + flex: 1; + padding: 1rem 0; +} + +.nav-item { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1.5rem; + color: rgba(255, 255, 255, 0.8); + text-decoration: none; + transition: all 0.2s; +} + +.nav-item:hover { + background: rgba(255, 255, 255, 0.1); + color: white; +} + +.nav-item.active { + background: rgba(255, 255, 255, 0.15); + color: white; + border-left: 3px solid white; +} + +.nav-icon { + font-size: 1.2rem; + width: 24px; + text-align: center; +} + +.nav-text { + font-size: 0.95rem; +} + +.sidebar-footer { + padding: 1rem 0; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +/* Portal Header */ +.portal-header { + background: white; + padding: 1rem 2rem; + border-bottom: 1px solid var(--gray-200); + display: flex; + align-items: center; + justify-content: space-between; + position: sticky; + top: 0; + z-index: 50; +} + +.header-left { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.header-title { + font-size: 1.5rem; + font-weight: 600; + color: var(--gray-900); + margin: 0; +} + +.breadcrumb { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + color: var(--gray-600); +} + +.breadcrumb a { + color: var(--primary-600); + text-decoration: none; +} + +.breadcrumb a:hover { + text-decoration: underline; +} + +.header-right { + display: flex; + align-items: center; + gap: 1rem; +} + +.icon-button { + position: relative; + background: none; + border: none; + font-size: 1.25rem; + cursor: pointer; + padding: 0.5rem; + border-radius: 8px; + transition: background 0.2s; +} + +.icon-button:hover { + background: var(--gray-100); +} + +.notification-badge { + position: absolute; + top: 0; + right: 0; + background: var(--danger-500); + color: white; + font-size: 0.7rem; + padding: 0.125rem 0.375rem; + border-radius: 10px; + min-width: 18px; + text-align: center; +} + +.user-menu { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.5rem; + border-radius: 8px; + cursor: pointer; + transition: background 0.2s; +} + +.user-menu:hover { + background: var(--gray-100); +} + +.user-avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: var(--primary-500); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; +} + +.user-name { + font-size: 0.9rem; + font-weight: 500; + color: var(--gray-700); +} + +.dropdown-arrow { + font-size: 0.75rem; + color: var(--gray-500); +} + +/* Cards and Content */ +.card { + background: white; + border-radius: 12px; + box-shadow: var(--shadow-sm); + margin-bottom: 1.5rem; + overflow: hidden; +} + +.card-header { + padding: 1.5rem; + border-bottom: 1px solid var(--gray-200); +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; + color: var(--gray-900); + margin: 0; +} + +.card-body { + padding: 1.5rem; +} + +/* Forms */ +.form-group { + margin-bottom: 1.25rem; +} + +.form-group label { + display: block; + margin-bottom: 0.5rem; + font-size: 0.9rem; + font-weight: 500; + color: var(--gray-700); +} + +.form-control { + width: 100%; + padding: 0.625rem 0.875rem; + border: 1px solid var(--gray-300); + border-radius: 8px; + font-size: 0.95rem; + transition: all 0.2s; +} + +.form-control:focus { + outline: none; + border-color: var(--primary-500); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + +.form-row { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1rem; +} + +/* Buttons */ +.btn { + padding: 0.625rem 1.25rem; + border-radius: 8px; + font-size: 0.95rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + border: none; + display: inline-flex; + align-items: center; + gap: 0.5rem; +} + +.btn-primary { + background: var(--primary-600); + color: white; +} + +.btn-primary:hover { + background: var(--primary-700); + transform: translateY(-1px); + box-shadow: var(--shadow-md); +} + +.btn-outline { + background: white; + border: 1px solid var(--gray-300); + color: var(--gray-700); +} + +.btn-outline:hover { + background: var(--gray-50); + border-color: var(--gray-400); +} + +.btn-sm { + padding: 0.375rem 0.75rem; + font-size: 0.875rem; +} + +.btn-lg { + padding: 0.75rem 1.5rem; + font-size: 1rem; +} + +/* Action Buttons */ +.action-buttons { + display: flex; + gap: 0.75rem; + margin-top: 1.5rem; +} + +/* Pagination */ +.pagination { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; + margin-top: 2rem; +} + +.pagination-info { + color: var(--gray-600); + font-size: 0.9rem; +} + +/* Summary Grid */ +.summary-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; + padding: 1rem; + background: var(--gray-50); + border-radius: 8px; +} + +.summary-item { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.summary-label { + font-size: 0.875rem; + color: var(--gray-600); +} + +/* Utility Classes */ +.text-sm { + font-size: 0.875rem; +} + +.text-muted { + color: var(--gray-600); +} + +.text-right { + text-align: right; +} + +/* Checkbox */ +.checkbox-label { + display: flex; + align-items: center; + gap: 0.5rem; + cursor: pointer; + margin-bottom: 0.5rem; +} + +.checkbox-label input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; +} + +/* Sidebar Toggle */ +.sidebar-toggle { + display: none; + background: none; + border: none; + color: white; + cursor: pointer; + padding: 0.5rem; +} + +.sidebar-toggle span { + display: block; + width: 20px; + height: 2px; + background: white; + margin: 4px 0; + transition: all 0.3s; +} + +/* Responsive */ +@media (max-width: 768px) { + .portal-sidebar { + transform: translateX(-100%); + transition: transform 0.3s; + } + + .portal-sidebar.active { + transform: translateX(0); + } + + .portal-main { + margin-left: 0; + } + + .sidebar-toggle { + display: block; + } + + .form-row { + grid-template-columns: 1fr; + } +} + +.dashboard-card { + background: var(--white); + border-radius: 12px; + padding: var(--space-md); + box-shadow: var(--card-shadow); + transition: var(--transition); +} + +.dashboard-card:hover { + box-shadow: var(--card-shadow-hover); + transform: translateY(-2px); +} + +.card-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-md); +} + +.card-title { + font-size: 1.1rem; + font-weight: 600; + color: var(--text-dark); +} + +.card-icon { + width: 48px; + height: 48px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + background: var(--gradient-warm); + color: var(--white); +} + +.card-value { + font-size: 2rem; + font-weight: 700; + color: var(--primary-blue); + margin-bottom: 0.5rem; +} + +.card-change { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.5rem; + border-radius: 4px; + font-size: 0.875rem; + font-weight: 500; +} + +.card-change.positive { + background: rgba(16, 185, 129, 0.1); + color: var(--success); +} + +.card-change.negative { + background: rgba(239, 68, 68, 0.1); + color: var(--error); +} + +/* Status Badges */ +.status-badge { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.875rem; + font-weight: 500; +} + +.status-badge.success { + background: rgba(16, 185, 129, 0.1); + color: var(--success); +} + +.status-badge.warning { + background: rgba(245, 158, 11, 0.1); + color: var(--warning); +} + +.status-badge.error { + background: rgba(239, 68, 68, 0.1); + color: var(--error); +} + +.status-badge.info { + background: rgba(59, 130, 246, 0.1); + color: var(--info); +} + +/* Tables */ +.data-table { + background: var(--white); + border-radius: 12px; + overflow: hidden; + box-shadow: var(--card-shadow); +} + +.table-header { + padding: var(--space-md); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: space-between; +} + +.table-actions { + display: flex; + gap: var(--space-sm); +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background: var(--soft-cream); +} + +th { + text-align: left; + padding: var(--space-sm) var(--space-md); + font-weight: 600; + color: var(--text-dark); + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +td { + padding: var(--space-md); + border-bottom: 1px solid var(--border); + color: var(--text-dark); +} + +tbody tr:hover { + background: var(--soft-cream); +} + +tbody tr:last-child td { + border-bottom: none; +} + +/* Forms */ +.portal-form { + background: var(--white); + border-radius: 12px; + padding: var(--space-lg); + box-shadow: var(--card-shadow); +} + +.form-section { + margin-bottom: var(--space-lg); +} + +.form-section-title { + font-size: 1.25rem; + font-weight: 600; + color: var(--text-dark); + margin-bottom: var(--space-md); + padding-bottom: var(--space-sm); + border-bottom: 2px solid var(--soft-cream); +} + +.form-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: var(--space-md); +} + +.form-group { + margin-bottom: var(--space-md); +} + +.form-label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: var(--text-dark); +} + +.form-label.required::after { + content: ' *'; + color: var(--error); +} + +.form-input, +.form-select, +.form-textarea { + width: 100%; + padding: 0.75rem; + border: 1px solid var(--border); + border-radius: 8px; + font-size: 1rem; + transition: var(--transition); +} + +.form-input:focus, +.form-select:focus, +.form-textarea:focus { + outline: none; + border-color: var(--warm-orange); + box-shadow: 0 0 0 3px rgba(220, 20, 60, 0.1); +} + +.form-textarea { + resize: vertical; + min-height: 120px; +} + +.form-help { + font-size: 0.875rem; + color: var(--text-light); + margin-top: 0.25rem; +} + +.form-error { + font-size: 0.875rem; + color: var(--error); + margin-top: 0.25rem; +} + +/* Buttons */ +.btn-portal { + padding: 0.75rem 1.5rem; + border-radius: 8px; + font-weight: 500; + border: none; + cursor: pointer; + transition: var(--transition); + display: inline-flex; + align-items: center; + gap: 0.5rem; +} + +.btn-portal-primary { + background: var(--gradient-warm); + color: var(--white); +} + +.btn-portal-primary:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(220, 20, 60, 0.3); +} + +.btn-portal-secondary { + background: var(--soft-cream); + color: var(--text-dark); + border: 1px solid var(--border); +} + +.btn-portal-secondary:hover { + background: var(--white); + border-color: var(--warm-orange); +} + +.btn-portal-danger { + background: var(--error); + color: var(--white); +} + +.btn-portal-danger:hover { + background: #dc2626; +} + +/* Upload Area */ +.upload-area { + border: 2px dashed var(--border); + border-radius: 12px; + padding: var(--space-lg); + text-align: center; + transition: var(--transition); + cursor: pointer; +} + +.upload-area:hover { + border-color: var(--warm-orange); + background: var(--soft-cream); +} + +.upload-area.dragging { + border-color: var(--warm-orange); + background: rgba(220, 20, 60, 0.05); +} + +.upload-icon { + width: 48px; + height: 48px; + margin: 0 auto var(--space-sm); + color: var(--text-light); +} + +/* Timeline */ +.timeline { + position: relative; + padding-left: var(--space-lg); +} + +.timeline::before { + content: ''; + position: absolute; + left: 15px; + top: 0; + bottom: 0; + width: 2px; + background: var(--border); +} + +.timeline-item { + position: relative; + margin-bottom: var(--space-md); +} + +.timeline-marker { + position: absolute; + left: -25px; + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--white); + border: 2px solid var(--warm-orange); +} + +.timeline-content { + background: var(--white); + padding: var(--space-md); + border-radius: 8px; + box-shadow: var(--card-shadow); +} + +.timeline-date { + font-size: 0.875rem; + color: var(--text-light); + margin-bottom: 0.5rem; +} + +/* Calendar */ +.calendar-widget { + background: var(--white); + border-radius: 12px; + padding: var(--space-md); + box-shadow: var(--card-shadow); +} + +.calendar-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-md); +} + +.calendar-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 2px; +} + +.calendar-day { + aspect-ratio: 1; + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + cursor: pointer; + transition: var(--transition); +} + +.calendar-day:hover { + background: var(--soft-cream); +} + +.calendar-day.active { + background: var(--gradient-warm); + color: var(--white); +} + +.calendar-day.has-event { + position: relative; +} + +.calendar-day.has-event::after { + content: ''; + position: absolute; + bottom: 4px; + width: 4px; + height: 4px; + border-radius: 50%; + background: var(--primary-orange); +} + +/* Login Page Specific */ +.login-container { + min-height: 100vh; + display: flex; +} + +.login-left { + flex: 1; + position: relative; + overflow: hidden; +} + +.login-video { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; +} + +.login-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, rgba(0, 31, 63, 0.8) 0%, rgba(220, 20, 60, 0.6) 100%); + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + color: var(--white); + padding: var(--space-lg); +} + +.login-brand { + text-align: center; + margin-bottom: var(--space-lg); +} + +.login-brand img { + width: 120px; + height: 120px; + margin-bottom: var(--space-md); + border-radius: 20px; +} + +.login-brand h1 { + font-family: var(--font-display); + font-size: 3rem; + margin-bottom: var(--space-sm); +} + +.login-tagline { + font-size: 1.25rem; + opacity: 0.9; +} + +.login-right { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + background: var(--white); + padding: var(--space-lg); +} + +.login-form-container { + width: 100%; + max-width: 400px; +} + +.login-form-header { + text-align: center; + margin-bottom: var(--space-lg); +} + +.login-form-title { + font-family: var(--font-display); + font-size: 2rem; + color: var(--text-dark); + margin-bottom: 0.5rem; +} + +.login-form-subtitle { + color: var(--text-light); +} + +.role-selector { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-sm); + margin-bottom: var(--space-md); +} + +.role-option { + padding: var(--space-sm); + border: 2px solid var(--border); + border-radius: 8px; + text-align: center; + cursor: pointer; + transition: var(--transition); +} + +.role-option:hover { + border-color: var(--warm-orange); +} + +.role-option.active { + border-color: var(--warm-orange); + background: rgba(220, 20, 60, 0.05); +} + +.divider { + text-align: center; + position: relative; + margin: var(--space-md) 0; +} + +.divider::before { + content: ''; + position: absolute; + top: 50%; + left: 0; + right: 0; + height: 1px; + background: var(--border); +} + +.divider span { + background: var(--white); + padding: 0 var(--space-sm); + position: relative; + color: var(--text-light); +} + +.oauth-buttons { + display: grid; + gap: var(--space-sm); +} + +.oauth-btn { + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-sm); + padding: 0.75rem; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--white); + cursor: pointer; + transition: var(--transition); +} + +.oauth-btn:hover { + background: var(--soft-cream); +} + +/* Responsive Design */ +@media (max-width: 1024px) { + .portal-sidebar { + transform: translateX(-100%); + } + + .portal-sidebar.open { + transform: translateX(0); + } + + .portal-main { + margin-left: 0; + } + + .dashboard-grid { + grid-template-columns: 1fr; + } + + .form-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 768px) { + .login-container { + flex-direction: column; + } + + .login-left { + height: 30vh; + } + + .portal-content { + padding: var(--space-md); + } + + .header-search { + display: none; + } + + .table-wrapper { + overflow-x: auto; + } +} + +/* Loading States */ +.skeleton { + background: linear-gradient(90deg, var(--soft-cream) 25%, var(--white) 50%, var(--soft-cream) 75%); + background-size: 200% 100%; + animation: loading 1.5s infinite; +} + +@keyframes loading { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} + +/* Tooltips */ +.tooltip { + position: relative; +} + +.tooltip::after { + content: attr(data-tooltip); + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + padding: 0.5rem 0.75rem; + background: var(--text-dark); + color: var(--white); + font-size: 0.875rem; + border-radius: 4px; + white-space: nowrap; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s; +} + +.tooltip:hover::after { + opacity: 1; +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fadeIn { + animation: fadeIn 0.5s ease-out; +} + +/* Print Styles */ +@media print { + .portal-sidebar, + .portal-header, + .btn-portal, + .table-actions { + display: none !important; + } + + .portal-main { + margin: 0 !important; + } + + .portal-content { + padding: 0 !important; + } +} diff --git a/website-mockups/css/styles.css b/website-mockups/css/styles.css new file mode 100644 index 0000000..5ce4582 --- /dev/null +++ b/website-mockups/css/styles.css @@ -0,0 +1,1366 @@ +/* HarborSmith - Main Styles */ +/* ========================= */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + + /* Base Colors - will be overridden by themes */ + --primary: #1e3a5f; + --accent: #dc2626; + --background: #ffffff; + --surface: #f8f9fa; + --text: #1f2937; + --text-secondary: #6b7280; + --border: #e5e7eb; + --overlay: rgba(0, 0, 0, 0.5); + + /* Spacing */ + --space-xs: 0.25rem; + --space-sm: 0.5rem; + --space-md: 1rem; + --space-lg: 1.5rem; + --space-xl: 2rem; + --space-2xl: 3rem; + --space-3xl: 4rem; + + /* Typography */ + --text-xs: 0.75rem; + --text-sm: 0.875rem; + --text-base: 1rem; + --text-lg: 1.125rem; + --text-xl: 1.25rem; + --text-2xl: 1.5rem; + --text-3xl: 1.875rem; + --text-4xl: 2.25rem; + --text-5xl: 3rem; + + /* Shadows */ + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1); + + /* Transitions */ + --transition-fast: 150ms ease; + --transition-base: 250ms ease; + --transition-slow: 350ms ease; + + /* Border Radius */ + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + --radius-full: 9999px; +} + +/* Base Styles */ +/* ========================= */ + +html { + scroll-behavior: smooth; +} + +body { + font-family: var(--font-family); + font-size: var(--text-base); + line-height: 1.6; + color: var(--text); + background-color: var(--background); + transition: background-color var(--transition-slow), color var(--transition-slow); +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-lg); +} + +/* Typography */ +/* ========================= */ + +h1, h2, h3, h4, h5, h6 { + font-weight: 700; + line-height: 1.2; + margin-bottom: var(--space-md); +} + +h1 { font-size: var(--text-5xl); } +h2 { font-size: var(--text-4xl); } +h3 { font-size: var(--text-3xl); } +h4 { font-size: var(--text-2xl); } +h5 { font-size: var(--text-xl); } +h6 { font-size: var(--text-lg); } + +p { + margin-bottom: var(--space-md); +} + +a { + color: var(--primary); + text-decoration: none; + transition: color var(--transition-fast); +} + +a:hover { + color: var(--accent); +} + +/* Navigation */ +/* ========================= */ + +.navbar { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + background: rgba(255, 255, 255, 0.98); + backdrop-filter: blur(10px); + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); + transition: all var(--transition-base); +} + +.navbar.scrolled { + background: rgba(255, 255, 255, 1); + backdrop-filter: blur(10px); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); +} + +.nav-container { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-lg) var(--space-xl); + max-width: 1400px; + margin: 0 auto; +} + +.nav-brand { + display: flex; + align-items: center; + gap: var(--space-sm); + font-size: var(--text-xl); + font-weight: 700; + color: var(--primary); +} + +.logo { + color: var(--primary); + border-radius: 50%; + object-fit: cover; +} + +.brand-text { + background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.nav-menu { + display: flex; + align-items: center; + gap: var(--space-xl); +} + +.nav-link { + position: relative; + padding: var(--space-sm) var(--space-md); + font-weight: 500; + color: var(--text); + transition: color var(--transition-fast); + border: none; + background: none; + cursor: pointer; + font-size: var(--text-base); + font-family: inherit; +} + +.nav-link:hover { + color: var(--primary); +} + +.nav-link.active { + color: var(--primary); +} + +.nav-link.active::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 30px; + height: 2px; + background: var(--accent); +} + +/* Mega Menu Dropdown */ +.nav-dropdown { + position: static; +} + +.dropdown-toggle { + display: flex; + align-items: center; + gap: var(--space-xs); +} + +.dropdown-toggle::after { + content: ''; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 5px solid currentColor; + transition: transform var(--transition-fast); +} + +.nav-dropdown:hover .dropdown-toggle::after { + transform: rotate(180deg); +} + +/* Mega Menu Container */ +.mega-menu { + position: absolute; + top: 100%; + left: 0; + right: 0; + background: var(--background); + border-top: 1px solid var(--border); + box-shadow: 0 10px 40px rgba(0,0,0,0.1); + opacity: 0; + visibility: hidden; + transform: translateY(-20px); + transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); + z-index: 1000; +} + +.nav-dropdown:hover .mega-menu { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.mega-menu-content { + max-width: 1200px; + margin: 0 auto; + padding: var(--space-2xl) var(--space-xl); + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: var(--space-3xl); +} + +.mega-menu-section { + display: flex; + flex-direction: column; + gap: var(--space-lg); +} + +.mega-menu-title { + font-size: var(--text-xs); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-secondary); + margin-bottom: var(--space-sm); +} + +.mega-menu-item { + display: flex; + align-items: start; + gap: var(--space-md); + padding: var(--space-md); + border-radius: var(--radius-lg); + color: var(--text); + transition: all var(--transition-fast); + text-decoration: none; +} + +.mega-menu-item:hover { + background: var(--surface); + transform: translateX(5px); +} + +.mega-menu-icon { + font-size: var(--text-3xl); + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + background: var(--surface); + border-radius: var(--radius-lg); + flex-shrink: 0; +} + +.mega-menu-item:hover .mega-menu-icon { + background: var(--primary); + color: white; +} + +.mega-menu-content-wrapper { + flex: 1; +} + +.mega-menu-item-title { + font-weight: 600; + font-size: var(--text-base); + margin-bottom: var(--space-xs); + color: var(--text); +} + +.mega-menu-item-desc { + font-size: var(--text-sm); + color: var(--text-secondary); + line-height: 1.4; +} + +/* Featured Section in Mega Menu */ +.mega-menu-featured { + background: var(--surface); + border-radius: var(--radius-xl); + padding: var(--space-xl); + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.mega-menu-featured-title { + font-size: var(--text-lg); + font-weight: 700; + color: var(--primary); + margin-bottom: var(--space-sm); +} + +.mega-menu-featured-desc { + color: var(--text-secondary); + margin-bottom: var(--space-lg); +} + +.mega-menu-featured .btn { + align-self: flex-start; +} + +/* Theme Switcher */ +.nav-actions { + display: flex; + align-items: center; + gap: var(--space-md); +} + +.theme-switcher { + position: relative; +} + +.theme-btn { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: var(--radius-full); + border: 2px solid var(--border); + background: var(--background); + color: var(--text); + cursor: pointer; + transition: all var(--transition-fast); +} + +.theme-btn:hover { + border-color: var(--primary); + transform: rotate(180deg); +} + +.theme-dropdown { + position: absolute; + top: calc(100% + var(--space-sm)); + right: 0; + min-width: 200px; + background: var(--background); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-xl); + opacity: 0; + visibility: hidden; + transform: translateY(-10px); + transition: all var(--transition-base); + padding: var(--space-sm); +} + +.theme-dropdown.active { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.theme-option { + display: flex; + align-items: center; + gap: var(--space-md); + width: 100%; + padding: var(--space-sm) var(--space-md); + border: none; + background: none; + color: var(--text); + cursor: pointer; + font-size: var(--text-sm); + font-family: inherit; + border-radius: var(--radius-md); + transition: all var(--transition-fast); +} + +.theme-option:hover { + background: var(--surface); +} + +.theme-option.active { + background: var(--primary); + color: white; +} + +.theme-colors { + display: flex; + gap: 2px; +} + +.theme-colors span { + width: 16px; + height: 16px; + border-radius: var(--radius-sm); +} + +/* Mobile Navigation Toggle */ +.nav-toggle { + display: none; + flex-direction: column; + gap: 4px; + padding: var(--space-sm); + background: none; + border: none; + cursor: pointer; +} + +.nav-toggle span { + width: 24px; + height: 2px; + background: var(--text); + transition: all var(--transition-base); +} + +.nav-toggle.active span:nth-child(1) { + transform: rotate(45deg) translate(5px, 5px); +} + +.nav-toggle.active span:nth-child(2) { + opacity: 0; +} + +.nav-toggle.active span:nth-child(3) { + transform: rotate(-45deg) translate(7px, -6px); +} + +/* Hero Section */ +/* ========================= */ + +.hero { + position: relative; + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.hero-background { + position: absolute; + inset: 0; + background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); + background-size: cover; + background-position: center; +} + +.hero-overlay { + position: absolute; + inset: 0; + background: linear-gradient(to bottom, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.5) 100%); +} + +/* Wave animation removed for cleaner design */ + +.hero-content { + position: relative; + z-index: 1; + text-align: center; + color: white; + padding: var(--space-2xl); +} + +.hero-title { + font-size: clamp(var(--text-3xl), 5vw, var(--text-5xl)); + font-weight: 800; + margin-bottom: var(--space-lg); + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); +} + +.hero-subtitle { + display: block; + font-size: var(--text-lg); + font-weight: 400; + opacity: 0.9; + margin-bottom: var(--space-sm); +} + +.hero-description { + font-size: var(--text-xl); + max-width: 600px; + margin: 0 auto var(--space-2xl); + opacity: 0.95; +} + +.hero-actions { + display: flex; + gap: var(--space-lg); + justify-content: center; + flex-wrap: wrap; +} + +.hero-scroll-indicator { + position: absolute; + bottom: var(--space-2xl); + left: 50%; + transform: translateX(-50%); + text-align: center; + color: white; + opacity: 0.8; + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-lg); +} + +.scroll-arrow { + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 8px solid white; + display: block; + animation: bounce 2s infinite; +} + +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } + 40% { transform: translateY(-10px); } + 60% { transform: translateY(-5px); } +} + +/* Buttons */ +/* ========================= */ + +.btn { + display: inline-flex; + align-items: center; + gap: var(--space-sm); + padding: var(--space-sm) var(--space-lg); + font-size: var(--text-base); + font-weight: 600; + font-family: inherit; + border: 2px solid transparent; + border-radius: var(--radius-full); + cursor: pointer; + transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); + text-decoration: none; + white-space: nowrap; + position: relative; + overflow: hidden; + z-index: 1; +} + +/* New modern animations - no ripple effect */ +.btn::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); + transition: left 0.5s; +} + +.btn:hover::before { + left: 100%; +} + +/* Primary button - gradient shift animation */ +.btn-primary { + background: linear-gradient(135deg, var(--primary) 0%, var(--primary) 50%, var(--accent) 50%, var(--accent) 100%); + background-size: 200% 100%; + background-position: 0% 0%; + color: white; + border-color: var(--primary); +} + +.btn-primary:hover { + background-position: 100% 0%; + transform: translateY(-3px) scale(1.02); + box-shadow: 0 10px 20px rgba(0,0,0,0.15); +} + +/* Secondary button - soft lift animation */ +.btn-secondary { + background: white; + color: var(--primary); + border-color: white; + box-shadow: 0 2px 5px rgba(0,0,0,0.1); +} + +.btn-secondary:hover { + background: white; + transform: translateY(-3px) scale(1.02); + box-shadow: 0 8px 20px rgba(0,0,0,0.15); +} + +/* Accent button - glow animation */ +.btn-accent { + background: var(--accent); + color: white; + border-color: var(--accent); + position: relative; +} + +.btn-accent:hover { + transform: translateY(-3px) scale(1.02); + box-shadow: 0 0 20px rgba(var(--accent-rgb, 220,53,69), 0.4), + 0 10px 20px rgba(0,0,0,0.15); +} + +/* Outline button - fill wipe animation */ +.btn-outline { + background: transparent; + color: var(--primary); + border-color: var(--primary); + position: relative; +} + +.btn-outline::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 0; + height: 100%; + background: var(--primary); + transition: width 0.3s ease; + z-index: -1; +} + +.btn-outline:hover { + color: white; + transform: translateY(-2px); +} + +.btn-outline:hover::after { + width: 100%; +} + +/* Outline light button - fill wipe animation */ +.btn-outline-light { + background: transparent; + color: white; + border-color: white; + position: relative; +} + +.btn-outline-light::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 0; + height: 100%; + background: white; + transition: width 0.3s ease; + z-index: -1; +} + +.btn-outline-light:hover { + color: var(--primary); + transform: translateY(-2px); +} + +.btn-outline-light:hover::after { + width: 100%; +} + +.btn-large { + padding: var(--space-md) var(--space-2xl); + font-size: var(--text-lg); +} + +.btn-icon { + transition: transform var(--transition-fast); +} + +.btn:hover .btn-icon { + transform: translateX(4px); +} + +.btn-icon-sm { + width: 16px; + height: 16px; +} + +/* Services Section */ +/* ========================= */ + +.services-split { + padding: var(--space-3xl) 0; + background: var(--surface); +} + +.section-header { + margin-bottom: var(--space-3xl); +} + +.section-title { + font-size: var(--text-4xl); + color: var(--text); + margin-bottom: var(--space-md); +} + +.section-subtitle { + font-size: var(--text-xl); + color: var(--text-secondary); +} + +.text-center { + text-align: center; +} + +.services-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + gap: var(--space-2xl); +} + +.service-card { + background: var(--background); + border-radius: var(--radius-xl); + overflow: hidden; + box-shadow: var(--shadow-lg); + transition: all var(--transition-base); +} + +.service-card:hover { + transform: translateY(-8px); + box-shadow: var(--shadow-xl); +} + +.service-image { + position: relative; + height: 250px; + background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); + background-size: cover; + background-position: center; + display: flex; + align-items: center; + justify-content: center; +} + +.charter-card .service-image { + background: linear-gradient(135deg, #0891b2 0%, #06b6d4 100%); +} + +.maintenance-card .service-image { + background: linear-gradient(135deg, #7c3aed 0%, #a855f7 100%); +} + +.service-overlay { + position: absolute; + inset: 0; + background: radial-gradient(circle at center, transparent 0%, rgba(0, 0, 0, 0.2) 100%); +} + +.service-icon { + position: relative; + z-index: 1; + color: white; + animation: float 3s ease-in-out infinite; +} + +@keyframes float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-10px); } +} + +.service-content { + padding: var(--space-2xl); +} + +.service-title { + font-size: var(--text-2xl); + margin-bottom: var(--space-md); +} + +.service-description { + color: var(--text-secondary); + margin-bottom: var(--space-lg); +} + +.service-features { + list-style: none; + margin-bottom: var(--space-xl); +} + +.service-features li { + position: relative; + padding-left: var(--space-xl); + margin-bottom: var(--space-sm); + color: var(--text-secondary); +} + +.service-features li::before { + content: 'โœ“'; + position: absolute; + left: 0; + color: var(--accent); + font-weight: bold; +} + +/* Stats Section */ +/* ========================= */ + +.stats-section { + padding: var(--space-3xl) 0; + background: var(--surface); + position: relative; + overflow: hidden; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--space-2xl); + position: relative; + z-index: 1; +} + +.stat-card { + text-align: center; + background: white; + padding: var(--space-2xl) var(--space-xl); + border-radius: var(--radius-xl); + border: 1px solid var(--border); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + transition: transform var(--transition-base), box-shadow var(--transition-base); +} + +.stat-card:hover { + transform: translateY(-5px); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); +} + +.stat-number { + font-size: var(--text-4xl); + font-weight: 800; + margin-bottom: var(--space-sm); + color: var(--primary); +} + +.stat-number::after { + content: '+'; +} + +.stat-label { + font-size: var(--text-lg); + color: var(--text-secondary); + font-weight: 500; +} + +/* Yacht Cards */ +/* ========================= */ + +.featured-yachts { + padding: var(--space-3xl) 0; +} + +.yacht-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); + gap: var(--space-2xl); +} + +.yacht-card { + background: var(--background); + border-radius: var(--radius-xl); + overflow: hidden; + box-shadow: var(--shadow-md); + transition: all var(--transition-base); +} + +.yacht-card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-xl); +} + +.yacht-image { + position: relative; + height: 250px; + background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); + background-size: cover; + background-position: center; + display: flex; + align-items: center; + justify-content: center; +} + +.yacht-badge { + position: absolute; + top: var(--space-md); + right: var(--space-md); + padding: var(--space-xs) var(--space-md); + background: var(--accent); + color: white; + font-size: var(--text-sm); + font-weight: 600; + border-radius: var(--radius-full); +} + +.badge-luxury { + background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%); +} + +.badge-value { + background: linear-gradient(135deg, #10b981 0%, #059669 100%); +} + +.yacht-overlay { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: opacity var(--transition-base); +} + +.yacht-card:hover .yacht-overlay { + opacity: 1; +} + +.yacht-view-btn { + padding: var(--space-sm) var(--space-lg); + background: white; + color: var(--primary); + border: none; + border-radius: var(--radius-full); + font-weight: 600; + cursor: pointer; + transition: all var(--transition-base); +} + +.yacht-view-btn:hover { + background: var(--primary); + color: white; + transform: scale(1.05); +} + +.yacht-content { + padding: var(--space-xl); +} + +.yacht-name { + font-size: var(--text-xl); + margin-bottom: var(--space-xs); +} + +.yacht-type { + color: var(--text-secondary); + margin-bottom: var(--space-md); +} + +.yacht-specs { + display: flex; + gap: var(--space-lg); + margin-bottom: var(--space-lg); +} + +.spec { + display: flex; + align-items: center; + gap: var(--space-sm); + font-size: var(--text-sm); + color: var(--text-secondary); +} + +.spec svg { + color: var(--primary); +} + +.spec-icon { + font-size: 1.2em; + color: var(--primary); + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; +} + +.yacht-price { + display: flex; + align-items: baseline; + gap: var(--space-sm); + margin-bottom: var(--space-lg); +} + +.price-label { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +.price-value { + font-size: var(--text-2xl); + font-weight: 700; + color: var(--primary); +} + +/* Testimonials */ +/* ========================= */ + +.testimonials { + padding: var(--space-3xl) 0; + background: var(--surface); +} + +.testimonials-slider { + position: relative; + max-width: 800px; + margin: 0 auto var(--space-2xl); +} + +.testimonial-card { + display: none; + background: var(--background); + border-radius: var(--radius-xl); + padding: var(--space-2xl); + box-shadow: var(--shadow-lg); +} + +.testimonial-card.active { + display: block; + animation: fadeIn var(--transition-slow); +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +.stars { + color: #fbbf24; + font-size: var(--text-xl); + margin-bottom: var(--space-lg); +} + +.testimonial-text { + font-size: var(--text-lg); + line-height: 1.8; + color: var(--text); + margin-bottom: var(--space-xl); + font-style: italic; +} + +.testimonial-author { + display: flex; + align-items: center; + gap: var(--space-md); +} + +.author-avatar { + width: 50px; + height: 50px; + border-radius: var(--radius-full); + background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; +} + +.author-name { + font-weight: 600; + color: var(--text); +} + +.author-title { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +.testimonial-dots { + display: flex; + justify-content: center; + gap: var(--space-sm); +} + +.dot { + width: 10px; + height: 10px; + border-radius: var(--radius-full); + background: var(--border); + border: none; + cursor: pointer; + transition: all var(--transition-base); +} + +.dot.active { + width: 30px; + background: var(--primary); +} + +/* CTA Section */ +/* ========================= */ + +.cta-section { + padding: var(--space-3xl) 0; + background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); +} + +.cta-content { + text-align: center; + color: white; +} + +.cta-title { + font-size: var(--text-4xl); + margin-bottom: var(--space-lg); +} + +.cta-description { + font-size: var(--text-xl); + max-width: 600px; + margin: 0 auto var(--space-2xl); + opacity: 0.95; +} + +.cta-actions { + display: flex; + gap: var(--space-lg); + justify-content: center; + flex-wrap: wrap; +} + +/* Footer */ +/* ========================= */ + +.footer { + background: var(--text); + color: white; + padding: var(--space-3xl) 0 var(--space-xl); +} + +.footer-content { + display: grid; + grid-template-columns: 2fr 1fr 1fr 2fr; + gap: var(--space-3xl); + margin-bottom: var(--space-3xl); +} + +.footer-brand { + display: flex; + align-items: center; + gap: var(--space-sm); + font-size: var(--text-xl); + font-weight: 700; + margin-bottom: var(--space-lg); +} + +.footer-logo { + color: white; +} + +.footer-description { + color: rgba(255, 255, 255, 0.8); + margin-bottom: var(--space-xl); +} + +.footer-social { + display: flex; + gap: var(--space-md); +} + +.social-link { + width: 40px; + height: 40px; + border-radius: var(--radius-full); + background: rgba(255, 255, 255, 0.1); + display: flex; + align-items: center; + justify-content: center; + color: white; + transition: all var(--transition-base); +} + +.social-link:hover { + background: var(--primary); + transform: translateY(-2px); +} + +.footer-title { + font-size: var(--text-lg); + margin-bottom: var(--space-lg); +} + +.footer-links { + list-style: none; +} + +.footer-links li { + margin-bottom: var(--space-sm); +} + +.footer-links a { + color: rgba(255, 255, 255, 0.8); + transition: color var(--transition-fast); +} + +.footer-links a:hover { + color: white; +} + +.footer-contact .contact-item { + display: flex; + align-items: center; + gap: var(--space-sm); + margin-bottom: var(--space-md); + color: rgba(255, 255, 255, 0.8); +} + +.footer-bottom { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: var(--space-2xl); + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.footer-copyright { + color: rgba(255, 255, 255, 0.6); +} + +.footer-legal { + display: flex; + gap: var(--space-xl); +} + +.footer-legal a { + color: rgba(255, 255, 255, 0.6); + transition: color var(--transition-fast); +} + +.footer-legal a:hover { + color: white; +} + +/* Responsive Design */ +/* ========================= */ + +@media (max-width: 1024px) { + .services-grid { + grid-template-columns: 1fr; + } + + .yacht-grid { + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + } + + .footer-content { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 768px) { + .nav-menu { + position: fixed; + top: 0; + right: -100%; + width: 80%; + height: 100vh; + background: var(--background); + flex-direction: column; + align-items: flex-start; + padding: var(--space-3xl) var(--space-xl); + box-shadow: var(--shadow-xl); + transition: right var(--transition-base); + } + + .nav-menu.active { + right: 0; + } + + .nav-toggle { + display: flex; + } + + .hero-title { + font-size: var(--text-3xl); + } + + .hero-description { + font-size: var(--text-lg); + } + + .hero-actions { + flex-direction: column; + align-items: center; + } + + .stats-grid { + grid-template-columns: repeat(2, 1fr); + } + + .footer-content { + grid-template-columns: 1fr; + } + + .footer-bottom { + flex-direction: column; + gap: var(--space-lg); + text-align: center; + } + + .footer-legal { + flex-direction: column; + gap: var(--space-sm); + } +} + +@media (max-width: 480px) { + .container { + padding: 0 var(--space-md); + } + + .services-grid { + grid-template-columns: 1fr; + } + + .yacht-grid { + grid-template-columns: 1fr; + } + + .yacht-specs { + flex-direction: column; + gap: var(--space-sm); + } +} \ No newline at end of file diff --git a/website-mockups/css/themes.css b/website-mockups/css/themes.css new file mode 100644 index 0000000..9a7edeb --- /dev/null +++ b/website-mockups/css/themes.css @@ -0,0 +1,174 @@ +/* HarborSmith - Theme Styles */ +/* ========================= */ + +/* Classical Nautical Theme (Default) - Navy & Crimson */ +[data-theme="nautical"] { + --primary: #001f3f; /* Classic navy blue */ + --accent: #dc143c; /* Nautical red/crimson */ + --background: #ffffff; + --surface: #f0f4f8; + --text: #0a1628; + --text-secondary: #4a5568; + --border: #cbd5e0; + --overlay: rgba(0, 31, 63, 0.7); + --gradient-start: #001f3f; + --gradient-end: #003366; +} + +/* Gold Theme - Navy with Gold accents */ +[data-theme="gold"] { + --primary: #001f3f; /* Classic navy blue */ + --accent: #bc970c; /* Gold */ + --background: #ffffff; + --surface: #f0f4f8; + --text: #0a1628; + --text-secondary: #4a5568; + --border: #cbd5e0; + --overlay: rgba(0, 31, 63, 0.7); + --gradient-start: #001f3f; + --gradient-end: #003366; +} + +/* Coastal Dawn Theme - Soft & Serene Luxury */ +[data-theme="coastal-dawn"] { + --primary: #A9B4C2; /* Cadet Blue */ + --accent: #D4AF37; /* Gilded Gold */ + --background: #F8F7F4; /* Alabaster White */ + --surface: #FFFFFF; + --text: #333745; /* Charcoal Slate */ + --text-secondary: #6B7280; + --border: #E5E7EB; + --overlay: rgba(169, 180, 194, 0.4); + --gradient-start: #A9B4C2; + --gradient-end: #C5D3E0; +} + +/* Deep Sea Slate Theme - Modern & Technical */ +[data-theme="deep-sea"] { + --primary: #1E2022; /* Gunmetal Grey */ + --accent: #00BFFF; /* Deep Sky Blue */ + --background: #1E2022; /* Dark background */ + --surface: #2A2D30; + --text: #E5E4E2; /* Platinum text */ + --text-secondary: #C0C0C0; /* Silver */ + --border: #3A3D40; + --overlay: rgba(30, 32, 34, 0.8); + --gradient-start: #1E2022; + --gradient-end: #2A2D30; +} + +/* Monaco White Theme - Pristine & Minimalist */ +[data-theme="monaco-white"] { + --primary: #2C3E50; /* Midnight Blue */ + --accent: #E74C3C; /* Pomegranate Red */ + --background: #FFFFFF; + --surface: #F8F9FA; + --text: #2C3E50; + --text-secondary: #7F8C8D; + --border: #ECF0F1; + --overlay: rgba(44, 62, 80, 0.05); + --gradient-start: #FFFFFF; + --gradient-end: #F8F9FA; +} + +/* Update hero gradient for all themes */ +[data-theme] .hero-background { + background: linear-gradient(135deg, var(--gradient-start) 0%, var(--gradient-end) 100%); +} + +/* Ensure good contrast for nav items */ +[data-theme] .nav-link { + color: var(--text); + font-weight: 500; +} + +[data-theme] .nav-link:hover { + color: var(--accent); +} + +/* Theme switcher button styling */ +[data-theme] .theme-switcher { + background: var(--surface); + border: 2px solid var(--border); + color: var(--text); +} + +[data-theme] .theme-switcher:hover { + background: var(--accent); + color: white; + border-color: var(--accent); +} + +/* Better button contrast */ +[data-theme] .btn-primary { + background: var(--accent); + color: white; + border: 2px solid var(--accent); +} + +[data-theme] .btn-primary:hover { + background: var(--primary); + border-color: var(--primary); +} + +/* Schedule Service button - make it filled */ +[data-theme] .btn-secondary { + background: var(--primary); + color: white; + border: 2px solid var(--primary); +} + +[data-theme] .btn-secondary:hover { + background: var(--accent); + border-color: var(--accent); + color: white; +} + +/* Card improvements */ +[data-theme] .service-card { + background: var(--surface); + border: 1px solid var(--border); +} + +[data-theme] .yacht-card { + background: white; + border: 1px solid var(--border); +} + +/* Stats section contrast */ +[data-theme] .stats-section { + background: var(--surface); +} + +/* Footer styling */ +[data-theme] footer { + background: var(--primary); + color: white; +} + +[data-theme] footer a { + color: rgba(255, 255, 255, 0.8); +} + +[data-theme] footer a:hover { + color: var(--accent); +} + +/* Special styling for Classical Nautical theme */ +[data-theme="nautical"] .btn-primary { + background: var(--accent); + box-shadow: 0 4px 6px rgba(220, 20, 60, 0.2); +} + +[data-theme="nautical"] .btn-secondary { + background: var(--primary); + box-shadow: 0 4px 6px rgba(0, 31, 63, 0.2); +} + +[data-theme="nautical"] .service-card { + border-color: var(--primary); +} + +[data-theme="nautical"] .yacht-card { + border-top: 3px solid var(--accent); +} \ No newline at end of file diff --git a/website-mockups/css/voyage-layout-red-backup.css b/website-mockups/css/voyage-layout-red-backup.css new file mode 100644 index 0000000..024a2ad --- /dev/null +++ b/website-mockups/css/voyage-layout-red-backup.css @@ -0,0 +1,1698 @@ +/* Voyage Layout - Warm, Cinematic, Inviting */ + +:root { + /* Default/Classical Nautical - Navy & Crimson */ + --primary-blue: #001f3f; + --warm-orange: #dc143c; + --warm-amber: #b91c3c; + --warm-yellow: #ef4444; + --soft-cream: #f0f4f8; + --text-dark: #0a1628; + --text-light: #4a5568; + --white: #ffffff; + --bg-light: #ffffff; + --border: #cbd5e0; + + /* Gradients */ + --gradient-warm: linear-gradient(135deg, #dc143c 0%, #ef4444 100%); + --gradient-sunset: linear-gradient(135deg, #b91c3c 0%, #dc143c 50%, #ef4444 100%); + --gradient-ocean: linear-gradient(135deg, #001f3f 0%, #003366 100%); + + /* Spacing */ + --space-xs: 0.5rem; + --space-sm: 1rem; + --space-md: 2rem; + --space-lg: 3rem; + --space-xl: 4rem; + --space-2xl: 6rem; + + /* Typography */ + --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + --font-display: 'Playfair Display', serif; + + /* Transitions */ + --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); +} + + +/* Coastal Dawn Theme - Soft, serene, golden accents */ +body.theme-coastal-dawn { + --primary-blue: #A9B4C2; + --warm-orange: #D4AF37; + --warm-amber: #C9A961; + --warm-yellow: #E6D088; + --soft-cream: #F8F7F4; + --text-dark: #333745; + --text-light: #6B7280; + --white: #FFFFFF; + --bg-light: #F8F7F4; + --border: #E5E7EB; + --gradient-warm: linear-gradient(135deg, #D4AF37 0%, #E6D088 100%); + --gradient-sunset: linear-gradient(135deg, #C9A961 0%, #D4AF37 50%, #E6D088 100%); + --gradient-ocean: linear-gradient(135deg, #A9B4C2 0%, #C5D3E0 100%); +} + +/* Deep Sea Theme - Dark, modern, electric blue accents */ +body.theme-deep-sea { + --primary-blue: #1E2022; + --warm-orange: #00BFFF; + --warm-amber: #1E90FF; + --warm-yellow: #4169E1; + --soft-cream: #2A2D30; + --text-dark: #E5E4E2; + --text-light: #C0C0C0; + --white: #1E2022; + --bg-light: #2A2D30; + --border: #3A3D40; + --gradient-warm: linear-gradient(135deg, #00BFFF 0%, #4169E1 100%); + --gradient-sunset: linear-gradient(135deg, #1E90FF 0%, #00BFFF 50%, #4169E1 100%); + --gradient-ocean: linear-gradient(135deg, #1E2022 0%, #2A2D30 100%); +} + +/* Monaco White Theme - Clean, minimalist, red accents */ +body.theme-monaco-white { + --primary-blue: #2C3E50; + --warm-orange: #E74C3C; + --warm-amber: #E67E22; + --warm-yellow: #F39C12; + --soft-cream: #F8F9FA; + --text-dark: #2C3E50; + --text-light: #7F8C8D; + --white: #FFFFFF; + --bg-light: #F8F9FA; + --border: #ECF0F1; + --gradient-warm: linear-gradient(135deg, #E74C3C 0%, #F39C12 100%); + --gradient-sunset: linear-gradient(135deg, #E67E22 0%, #E74C3C 50%, #F39C12 100%); + --gradient-ocean: linear-gradient(135deg, #2C3E50 0%, #34495E 100%); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* Global image fix to ensure images display */ +img { + max-width: 100%; + height: auto; + display: block; +} + +/* Lucide Icons Styling */ +[data-lucide] { + width: 24px; + height: 24px; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; + fill: none; + vertical-align: middle; +} + +.btn-icon { + width: 20px; + height: 20px; + display: inline-block; + margin-right: 8px; +} + +.spec-icon { + width: 20px; + height: 20px; + display: inline-block; + margin-right: 8px; + color: var(--warm-orange); +} + +.feature-icon i { + width: 32px; + height: 32px; + color: var(--warm-orange); +} + +.booking-icon i { + width: 48px; + height: 48px; + color: var(--warm-orange); + margin-bottom: 1rem; +} + +.footer-icon { + width: 16px; + height: 16px; + display: inline-block; + margin-right: 8px; + vertical-align: text-bottom; +} + +.social-links a { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.1); + transition: var(--transition); +} + +.social-links a:hover { + background: var(--warm-orange); +} + +.social-links a i { + width: 20px; + height: 20px; + color: white !important; + stroke: white !important; +} + +.social-links a svg { + stroke: white !important; + color: white !important; +} + +/* Star rating icons */ +.stars-icons { + display: inline-flex; + gap: 2px; +} + +.star-filled { + width: 16px; + height: 16px; + fill: var(--warm-yellow); + stroke: var(--warm-yellow); +} + +body { + font-family: var(--font-sans); + color: var(--text-dark); + background: var(--bg-light); + overflow-x: hidden; + line-height: 1.6; + transition: background 0.3s ease, color 0.3s ease; +} + +/* Smooth Scrolling */ +html { + scroll-behavior: smooth; +} + +/* Theme toggle button */ +.theme-toggle { + background: transparent; + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: var(--transition); + margin-left: 1rem; +} + +.theme-toggle:hover { + background: rgba(255, 255, 255, 0.1); + border-color: var(--warm-orange); +} + +.theme-toggle i { + width: 20px; + height: 20px; + color: white; +} + +.voyage-nav.scrolled .theme-toggle { + border-color: var(--primary-blue); +} + +.voyage-nav.scrolled .theme-toggle i { + color: var(--primary-blue); +} + +.voyage-nav.scrolled .theme-toggle:hover { + background: rgba(0, 31, 63, 0.1); + border-color: var(--warm-orange); +} + +/* Navigation */ +.voyage-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + background: rgba(255, 255, 255, 0); + backdrop-filter: blur(0); + transition: var(--transition-slow); + padding: var(--space-md) 0; +} + +.voyage-nav.scrolled { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + padding: var(--space-sm) 0; +} + +.nav-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); + display: flex; + justify-content: space-between; + align-items: center; +} + +.nav-brand { + display: flex; + align-items: center; + gap: var(--space-sm); + font-family: var(--font-display); + font-size: 1.5rem; + font-weight: 700; + color: var(--white); + transition: var(--transition); +} + +.voyage-nav.scrolled .nav-brand { + color: var(--primary-blue); +} + +.nav-logo { + height: 50px; + width: auto; + object-fit: contain; +} + +.nav-links { + display: flex; + gap: var(--space-lg); + align-items: center; +} + +.nav-link { + color: var(--white); + text-decoration: none; + font-weight: 500; + transition: var(--transition); + position: relative; +} + +.voyage-nav.scrolled .nav-link { + color: var(--text-dark); +} + +.nav-link::after { + content: ''; + position: absolute; + bottom: -4px; + left: 0; + width: 0; + height: 2px; + background: var(--warm-orange); + transition: var(--transition); +} + +.nav-link:hover::after { + width: 100%; +} + +.nav-cta { + background: var(--gradient-warm); + color: var(--white) !important; + padding: 0.75rem 1.5rem; + border-radius: 50px; + transition: var(--transition); +} + +.nav-cta:hover { + transform: translateY(-2px); + box-shadow: 0 10px 20px rgba(249, 115, 22, 0.3); +} + +/* Theme Switcher */ +.theme-switcher { + position: relative; + margin-left: var(--space-md); +} + +.theme-btn { + background: transparent; + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: var(--transition); + color: var(--white); +} + +.voyage-nav.scrolled .theme-btn { + color: var(--primary-blue); + border-color: var(--primary-blue); +} + +.theme-btn:hover { + background: rgba(255, 255, 255, 0.1); + transform: rotate(180deg); +} + +.voyage-nav.scrolled .theme-btn:hover { + background: rgba(30, 58, 95, 0.1); +} + +.theme-dropdown { + position: absolute; + top: 50px; + right: 0; + background: var(--white); + border-radius: 12px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15); + padding: var(--space-sm); + min-width: 200px; + opacity: 0; + visibility: hidden; + transform: translateY(-10px); + transition: var(--transition); +} + +.theme-dropdown.active { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.theme-option { + display: flex; + align-items: center; + gap: var(--space-sm); + width: 100%; + padding: 0.75rem; + background: transparent; + border: none; + border-radius: 8px; + cursor: pointer; + transition: var(--transition); + text-align: left; +} + +.theme-option:hover { + background: var(--bg-light); +} + +.theme-colors { + display: flex; + gap: 2px; +} + +.theme-colors span { + width: 16px; + height: 16px; + border-radius: 50%; + border: 1px solid rgba(0, 0, 0, 0.1); +} + +/* Hero Section */ +.hero-voyage { + position: relative; + height: 100vh; + min-height: 600px; + overflow: hidden; +} + +.hero-video-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1; +} + +.hero-video { + width: 100%; + height: 100%; + object-fit: cover; +} + +.hero-image-fallback { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + z-index: -1; +} + +.hero-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 2; +} + +.gradient-warm { + background: linear-gradient(to bottom, + rgba(0, 31, 63, 0.3) 0%, + rgba(0, 31, 63, 0.5) 50%, + rgba(0, 31, 63, 0.7) 100%); +} + +.gradient-depth { + background: linear-gradient(to right, + rgba(220, 20, 60, 0.1) 0%, + transparent 100%); +} + +.hero-content { + position: relative; + z-index: 10; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + padding: var(--space-md); +} + +.trust-badge { + display: inline-flex; + align-items: center; + gap: var(--space-sm); + padding: 0.5rem 1.5rem; + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 50px; + color: var(--white) !important; + font-size: 0.875rem; + margin-bottom: var(--space-lg); + z-index: 10; + position: relative; +} + +/* Ensure the trust badge text is white */ +.trust-badge > span:last-child { + color: var(--white) !important; +} + +/* Make sure the headline is visible */ +.hero-headline { + display: block !important; + visibility: visible !important; +} + +.gradient-text { + display: inline-block !important; + visibility: visible !important; +} + +/* Make absolutely sure the trust badge text is white and visible */ +.trust-badge span:last-child { + display: inline !important; + color: white !important; + visibility: visible !important; +} + +.stars { + color: var(--warm-yellow); + font-size: 1rem; +} + +.hero-headline { + font-family: var(--font-display); + font-size: clamp(3rem, 8vw, 6rem); + font-weight: 900; + line-height: 1.1; + margin-bottom: var(--space-md); +} + +.gradient-text { + background: linear-gradient(135deg, + #ffffff 0%, + #f0f4f8 25%, + #e2e8f0 50%, + #cbd5e0 75%, + #94a3b8 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.hero-subtext { + font-size: 1.25rem; + color: rgba(255, 255, 255, 0.9); + max-width: 800px; + margin: 0 auto var(--space-lg); + font-weight: 300; + line-height: 1.8; +} + +.hero-actions { + display: flex; + gap: var(--space-md); + flex-wrap: wrap; + justify-content: center; +} + +.btn-primary-warm, +.btn-secondary-warm { + display: inline-flex; + align-items: center; + gap: var(--space-xs); + padding: 1rem 2rem; + font-size: 1.125rem; + font-weight: 600; + border: none; + border-radius: 50px; + cursor: pointer; + transition: var(--transition); + text-decoration: none; +} + +.btn-primary-warm { + background: var(--gradient-warm); + color: var(--white); + box-shadow: 0 10px 30px rgba(220, 20, 60, 0.3); +} + +.btn-primary-warm:hover { + transform: translateY(-3px); + box-shadow: 0 15px 40px rgba(220, 20, 60, 0.4); +} + +.btn-secondary-warm { + background: rgba(255, 255, 255, 0.1); + color: var(--white); + border: 2px solid rgba(255, 255, 255, 0.3); + backdrop-filter: blur(10px); +} + +.btn-secondary-warm:hover { + background: rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.5); + transform: translateY(-3px); +} + +.btn-icon { + font-size: 1.25rem; +} + +/* Scroll Indicator */ +.scroll-indicator { + position: absolute; + bottom: 2rem; + left: 50%; + transform: translateX(-50%); + color: rgba(255, 255, 255, 0.6); + font-size: 0.875rem; + animation: bounce 2s infinite; +} + +/* Updated scroll arrow styles */ +.scroll-arrow { + margin-top: 0.5rem; + display: flex; + justify-content: center; + align-items: center; +} + +.scroll-arrow i { + width: 32px; + height: 32px; + color: rgba(255, 255, 255, 0.6); + animation: arrow-bounce 1.5s infinite; +} + +@keyframes arrow-bounce { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(8px); + } +} + +/* Decorations */ +.hero-decoration { + position: absolute; + z-index: 5; +} + +.top-right { + top: 5rem; + right: 5rem; +} + +.bottom-left { + bottom: 8rem; + left: 5rem; +} + +.decoration-circle { + width: 80px; + height: 80px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 2rem; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.1); + animation: float 6s ease-in-out infinite; +} + +.decoration-circle.orange { + background: linear-gradient(135deg, + rgba(220, 20, 60, 0.2) 0%, + rgba(239, 68, 68, 0.2) 100%); +} + +.decoration-circle.blue { + background: linear-gradient(135deg, + rgba(0, 31, 63, 0.2) 0%, + rgba(0, 51, 102, 0.2) 100%); +} + +/* Welcome Section */ +.welcome-section { + padding: var(--space-2xl) 0; + background: linear-gradient(to bottom, var(--white) 0%, var(--bg-light) 100%); +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); +} + +.welcome-content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-2xl); + align-items: center; +} + +.section-title { + font-family: var(--font-display); + font-size: 3rem; + font-weight: 700; + margin-bottom: var(--space-md); + color: var(--primary-blue); +} + +.section-title.warm { + background: var(--gradient-warm); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.lead-text { + font-size: 1.25rem; + color: var(--text-light); + margin-bottom: var(--space-lg); + line-height: 1.8; +} + +.feature-list { + display: flex; + flex-direction: column; + gap: var(--space-md); +} + +.feature-item { + display: flex; + gap: var(--space-md); + align-items: flex-start; +} + +.feature-icon { + font-size: 2rem; + background: var(--gradient-warm); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.feature-item h4 { + font-size: 1.25rem; + margin-bottom: 0.25rem; + color: var(--text-dark); +} + +.feature-item p { + color: var(--text-light); +} + +.welcome-image { + position: relative; +} + +.rounded-image { + width: 100%; + border-radius: 20px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1); +} + +.image-badge { + position: absolute; + bottom: 2rem; + right: 2rem; + background: var(--gradient-warm); + color: var(--white); + padding: 1rem 1.5rem; + border-radius: 15px; + font-weight: 600; + display: flex; + flex-direction: column; + align-items: center; + box-shadow: 0 10px 30px rgba(220, 20, 60, 0.3); +} + +/* Fleet Showcase */ +.fleet-showcase { + padding: var(--space-2xl) 0; + background: var(--white); +} + +.section-header { + text-align: center; + margin-bottom: var(--space-2xl); +} + +.section-subtitle { + font-size: 1.25rem; + color: var(--text-light); + margin-top: var(--space-sm); +} + +.fleet-carousel { + position: relative; + max-width: 1000px; + margin: 0 auto; +} + +.yacht-card { + display: none; + grid-template-columns: 1fr 1fr; + gap: var(--space-lg); + align-items: center; + background: var(--white); + border-radius: 20px; + overflow: hidden; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08); +} + +.yacht-card.active { + display: grid; + animation: fadeIn 0.6s ease; +} + +.yacht-image-container { + position: relative; + height: 500px; + overflow: hidden; +} + +.yacht-image-container img { + width: 100%; + height: 100%; + object-fit: cover; + transition: var(--transition-slow); +} + +.yacht-card:hover .yacht-image-container img { + transform: scale(1.05); +} + +.yacht-badge { + position: absolute; + top: 2rem; + left: 2rem; + padding: 0.5rem 1rem; + border-radius: 50px; + font-weight: 600; + font-size: 0.875rem; + backdrop-filter: blur(10px); +} + +.yacht-badge.premium { + background: var(--gradient-warm); + color: var(--white); +} + +.yacht-badge.adventure { + background: var(--gradient-ocean); + color: var(--white); +} + +.yacht-details { + padding: var(--space-lg); +} + +.yacht-name { + font-family: var(--font-display); + font-size: 2.5rem; + margin-bottom: var(--space-sm); + color: var(--primary-blue); +} + +.yacht-description { + color: var(--text-light); + margin-bottom: var(--space-md); + line-height: 1.8; +} + +.yacht-specs { + display: flex; + flex-direction: column; + gap: var(--space-sm); + margin-bottom: var(--space-md); +} + +.spec { + display: flex; + align-items: center; + gap: var(--space-sm); +} + +.spec-icon { + font-size: 1.25rem; +} + +.yacht-pricing { + display: flex; + align-items: baseline; + gap: var(--space-xs); + margin-bottom: var(--space-md); +} + +.price-from { + color: var(--text-light); + font-size: 0.875rem; +} + +.price-amount { + font-size: 2rem; + font-weight: 700; + color: var(--warm-orange); +} + +.btn-book-yacht { + width: 100%; + padding: 1rem; + background: var(--gradient-warm); + color: var(--white); + border: none; + border-radius: 12px; + font-size: 1.125rem; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.btn-book-yacht:hover { + transform: translateY(-2px); + box-shadow: 0 10px 30px rgba(220, 20, 60, 0.3); +} + +.fleet-nav { + display: flex; + justify-content: center; + align-items: center; + gap: var(--space-md); + margin-top: var(--space-lg); +} + +.fleet-prev, +.fleet-next { + width: 50px; + height: 50px; + border-radius: 50%; + border: 2px solid var(--warm-orange); + background: var(--white); + color: var(--warm-orange); + font-size: 1.5rem; + cursor: pointer; + transition: var(--transition); +} + +.fleet-prev:hover, +.fleet-next:hover { + background: var(--gradient-warm); + color: var(--white); + border-color: transparent; +} + +.fleet-dots { + display: flex; + gap: var(--space-sm); +} + +.dot { + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--text-light); + opacity: 0.3; + cursor: pointer; + transition: var(--transition); +} + +.dot.active { + background: var(--warm-orange); + opacity: 1; + transform: scale(1.2); +} + +/* Services Section */ +.services-section { + padding: var(--space-2xl) 0; + background: linear-gradient(135deg, var(--soft-cream) 0%, var(--white) 100%); +} + +.services-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: var(--space-lg); + margin: var(--space-xl) 0; +} + +.service-card { + background: var(--white); + border-radius: 20px; + padding: var(--space-xl); + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08); + transition: var(--transition); + position: relative; + overflow: hidden; +} + +.service-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 5px; + background: var(--gradient-warm); +} + +.charter-service::before { + background: var(--gradient-ocean); +} + +.service-card:hover { + transform: translateY(-5px); + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.12); +} + +.service-icon-wrapper { + width: 80px; + height: 80px; + background: var(--gradient-warm); + border-radius: 20px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: var(--space-md); +} + +.charter-service .service-icon-wrapper { + background: var(--gradient-ocean); +} + +.service-icon { + width: 40px; + height: 40px; + color: white; +} + +.service-card h3 { + font-family: var(--font-display); + font-size: 2rem; + margin-bottom: var(--space-sm); + color: var(--text-dark); +} + +.service-card p { + color: var(--text-light); + margin-bottom: var(--space-md); + line-height: 1.6; +} + +.service-features { + list-style: none; + padding: 0; + margin: var(--space-md) 0; +} + +.service-features li { + padding: 0.5rem 0; + display: flex; + align-items: center; + color: var(--text-dark); +} + +.check-icon { + width: 20px; + height: 20px; + color: var(--warm-orange); + margin-right: 0.75rem; + flex-shrink: 0; +} + +.btn-service { + width: 100%; + padding: 1rem 2rem; + background: var(--gradient-warm); + color: white; + border: none; + border-radius: 12px; + font-weight: 600; + font-size: 1rem; + cursor: pointer; + transition: var(--transition); + margin-top: var(--space-md); +} + +.charter-service .btn-service { + background: var(--gradient-ocean); +} + +.btn-service:hover { + transform: scale(1.02); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); +} + +/* Service Stats */ +.service-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--space-md); + margin-top: var(--space-xl); + padding: var(--space-lg); + background: white; + border-radius: 20px; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05); +} + +.stat-item { + text-align: center; + padding: var(--space-md); +} + +.stat-icon { + width: 40px; + height: 40px; + color: var(--warm-orange); + margin-bottom: var(--space-sm); +} + +.stat-number { + display: block; + font-size: 2.5rem; + font-weight: 800; + color: var(--primary-blue); + font-family: var(--font-display); +} + +.stat-label { + display: block; + color: var(--text-light); + font-size: 0.875rem; + margin-top: 0.5rem; +} + +/* Experience Stories */ +.experience-stories { + padding: var(--space-2xl) 0; + background: var(--bg-light); +} + +.story-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); +} + +.section-title.center { + text-align: center; + margin-bottom: var(--space-2xl); +} + +.stories-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: var(--space-lg); +} + +.story-card { + background: var(--white); + border-radius: 20px; + overflow: hidden; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); + transition: var(--transition); +} + +.story-card:hover { + transform: translateY(-5px); + box-shadow: 0 15px 40px rgba(0, 0, 0, 0.12); +} + +.story-image { + position: relative; + height: 250px; + overflow: hidden; +} + +.story-image img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.story-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(to bottom, transparent 0%, rgba(0, 0, 0, 0.5) 100%); + display: flex; + align-items: flex-end; + padding: var(--space-md); +} + +.story-category { + background: var(--gradient-warm); + color: var(--white); + padding: 0.5rem 1rem; + border-radius: 50px; + font-size: 0.875rem; + font-weight: 600; +} + +.story-content { + padding: var(--space-md); +} + +.story-content h3 { + font-family: var(--font-display); + font-size: 1.5rem; + margin-bottom: var(--space-sm); + color: var(--text-dark); +} + +.story-content p { + color: var(--text-light); + line-height: 1.6; + margin-bottom: var(--space-sm); +} + +.story-link { + color: var(--warm-orange); + text-decoration: none; + font-weight: 600; + transition: var(--transition); +} + +.story-link:hover { + color: var(--warm-amber); +} + +/* Gallery Section */ +.gallery-section { + padding: var(--space-2xl) 0; + background: var(--white); +} + +.image-gallery { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--space-md); + margin-top: var(--space-lg); +} + +.gallery-item { + position: relative; + border-radius: 15px; + overflow: hidden; + height: 300px; + cursor: pointer; + transition: var(--transition); +} + +.gallery-item.large { + grid-column: span 2; + height: 400px; +} + +.gallery-item img { + width: 100%; + height: 100%; + object-fit: cover; + transition: var(--transition-slow); +} + +.gallery-overlay { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: linear-gradient(to top, rgba(0, 31, 63, 0.9) 0%, transparent 100%); + padding: var(--space-lg) var(--space-md) var(--space-md); + transform: translateY(100%); + transition: var(--transition); +} + +.gallery-caption { + color: var(--white); + font-size: 1.25rem; + font-weight: 600; +} + +.gallery-item:hover .gallery-overlay { + transform: translateY(0); +} + +.gallery-item:hover img { + transform: scale(1.1); +} + +@media (max-width: 768px) { + .gallery-item.large { + grid-column: span 1; + height: 300px; + } +} + +/* Booking CTA */ +.booking-cta { + padding: var(--space-2xl) 0; + background: linear-gradient(135deg, #001f3f 0%, #003366 100%); +} + +.booking-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); +} + +.booking-content { + text-align: center; +} + +.booking-title { + font-family: var(--font-display); + font-size: 3rem; + color: var(--white); + margin-bottom: var(--space-sm); +} + +.booking-subtitle { + font-size: 1.25rem; + color: rgba(255, 255, 255, 0.8); + margin-bottom: var(--space-2xl); +} + +.booking-options { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--space-lg); +} + +.booking-card { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 20px; + padding: var(--space-lg); + text-align: center; + transition: var(--transition); +} + +.booking-card:hover { + background: rgba(255, 255, 255, 0.15); + transform: translateY(-5px); +} + +.booking-card.featured { + background: var(--gradient-warm); + border: none; + transform: scale(1.05); +} + +.booking-icon { + font-size: 3rem; + margin-bottom: var(--space-sm); +} + +.booking-card h3 { + font-size: 1.5rem; + color: var(--white); + margin-bottom: var(--space-xs); +} + +.booking-card p { + color: rgba(255, 255, 255, 0.8); + margin-bottom: var(--space-md); +} + +.btn-booking { + width: 100%; + padding: 0.75rem 1.5rem; + background: rgba(255, 255, 255, 0.2); + color: var(--white); + border: 2px solid var(--white); + border-radius: 50px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.btn-booking:hover { + background: var(--white); + color: var(--primary-blue); +} + +.btn-booking.primary { + background: var(--white); + color: var(--warm-orange); + border-color: var(--white); +} + +.btn-booking.primary:hover { + background: var(--gradient-warm); + color: var(--white); +} + +/* Footer */ +.voyage-footer { + background: var(--primary-blue); + color: var(--white); + padding: var(--space-2xl) 0 var(--space-md); +} + +.footer-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); +} + +.footer-content { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + gap: var(--space-2xl); + margin-bottom: var(--space-lg); +} + +.footer-brand h3 { + font-family: var(--font-display); + font-size: 2rem; + margin-bottom: var(--space-sm); +} + +.footer-logo { + width: 50px; + height: 50px; + border-radius: 50%; + margin-bottom: var(--space-sm); +} + +.footer-brand p { + color: rgba(255, 255, 255, 0.7); + margin-bottom: var(--space-md); +} + +.social-links { + display: flex; + gap: var(--space-sm); +} + +.social-links a { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.1); + border-radius: 50%; + font-size: 1.25rem; + transition: var(--transition); +} + +.social-links a:hover { + background: var(--gradient-warm); + transform: translateY(-3px); +} + +.footer-links h4, +.footer-contact h4 { + margin-bottom: var(--space-md); +} + +/* Force social icons to be white */ +.voyage-footer .social-links a svg, +.voyage-footer .social-links a i { + stroke: white !important; + color: white !important; + fill: none !important; +} + +.footer-links a { + display: block; + color: rgba(255, 255, 255, 0.7); + text-decoration: none; + margin-bottom: var(--space-xs); + transition: var(--transition); +} + +.footer-links a:hover { + color: var(--warm-orange); +} + +.footer-contact p { + color: rgba(255, 255, 255, 0.7); + margin-bottom: var(--space-xs); +} + +.footer-bottom { + text-align: center; + padding-top: var(--space-lg); + border-top: 1px solid rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.5); +} + +/* Layout Switcher */ +.layout-switcher { + position: fixed; + bottom: 2rem; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 0.5rem; + background: var(--white); + padding: 0.5rem; + border-radius: 50px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); + z-index: 90; +} + +.layout-btn { + padding: 0.5rem 1rem; + background: transparent; + border: none; + border-radius: 50px; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: var(--transition); + color: var(--text-dark); +} + +.layout-btn:hover { + background: var(--bg-light); +} + +.layout-btn.active { + background: var(--gradient-warm); + color: var(--white); +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { + transform: translateX(-50%) translateY(0); + } + 40% { + transform: translateX(-50%) translateY(-10px); + } + 60% { + transform: translateX(-50%) translateY(-5px); + } +} + +@keyframes scroll { + 0% { + top: 8px; + opacity: 1; + } + 100% { + top: 20px; + opacity: 0; + } +} + +@keyframes float { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-20px); + } +} + +.animate-fade-in { + animation: fadeIn 0.8s ease; +} + +.animate-fade-up { + animation: fadeIn 0.8s ease 0.2s both; +} + +.animate-fade-up-delay { + animation: fadeIn 0.8s ease 0.4s both; +} + +.animate-fade-up-delay-2 { + animation: fadeIn 0.8s ease 0.6s both; +} + +/* Responsive */ +@media (max-width: 768px) { + .hero-headline { + font-size: 3rem; + } + + .hero-subtext { + font-size: 1rem; + } + + .hero-actions { + flex-direction: column; + width: 100%; + padding: 0 var(--space-md); + } + + .btn-primary-warm, + .btn-secondary-warm { + width: 100%; + } + + .welcome-content { + grid-template-columns: 1fr; + } + + .yacht-card { + grid-template-columns: 1fr; + } + + .footer-content { + grid-template-columns: 1fr; + text-align: center; + } + + .social-links { + justify-content: center; + } + + .top-right, + .bottom-left { + display: none; + } + + .nav-links { + display: none; + } +} + +/* Dark theme adjustments for Deep Sea */ +body.theme-deep-sea .welcome-section, +body.theme-deep-sea .fleet-showcase, +body.theme-deep-sea .experience-stories, +body.theme-deep-sea .booking-cta { + background: var(--bg-light); +} + +/* This file preserves the original red/crimson theme for future use */ +/* The gold theme section has been removed as it's now the default */ + +body.theme-deep-sea .yacht-card, +body.theme-deep-sea .story-card, +body.theme-deep-sea .booking-card { + background: var(--soft-cream); + color: var(--text-dark); +} + +body.theme-deep-sea .hero-content h1, +body.theme-deep-sea .hero-content p { + color: var(--text-dark); +} + +body.theme-deep-sea .voyage-nav:not(.scrolled) .nav-brand, +body.theme-deep-sea .voyage-nav:not(.scrolled) .nav-link { + color: var(--text-dark); +} + +body.theme-deep-sea .voyage-footer { + background: var(--primary-blue); + color: var(--text-dark); +} + +/* Theme-specific button colors */ +body.theme-coastal-dawn .btn-primary-warm, +body.theme-coastal-dawn .btn-secondary-warm { + background: var(--warm-orange); + color: var(--white); +} + +body.theme-deep-sea .btn-primary-warm, +body.theme-deep-sea .btn-secondary-warm { + background: var(--warm-orange); + color: var(--white); +} + +body.theme-monaco-white .btn-primary-warm, +body.theme-monaco-white .btn-secondary-warm { + background: var(--warm-orange); + color: var(--white); +} \ No newline at end of file diff --git a/website-mockups/css/voyage-layout.css b/website-mockups/css/voyage-layout.css new file mode 100644 index 0000000..f3b14ef --- /dev/null +++ b/website-mockups/css/voyage-layout.css @@ -0,0 +1,1659 @@ +/* Voyage Layout - Warm, Cinematic, Inviting */ + +:root { + /* Default Theme - Navy & Gold */ + --primary-blue: #001f3f; + --warm-orange: #b48b4e; + --warm-amber: #9d7943; + --warm-yellow: #c9a56f; + --soft-cream: #f0f4f8; + --text-dark: #0a1628; + --text-light: #4a5568; + --white: #ffffff; + --bg-light: #ffffff; + --border: #cbd5e0; + + /* Gradients */ + --gradient-warm: linear-gradient(135deg, #b48b4e 0%, #c9a56f 100%); + --gradient-sunset: linear-gradient(135deg, #9d7943 0%, #b48b4e 50%, #c9a56f 100%); + --gradient-ocean: linear-gradient(135deg, #001f3f 0%, #003366 100%); + + /* Spacing */ + --space-xs: 0.5rem; + --space-sm: 1rem; + --space-md: 2rem; + --space-lg: 3rem; + --space-xl: 4rem; + --space-2xl: 6rem; + + /* Typography */ + --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + --font-display: 'Playfair Display', serif; + + /* Transitions */ + --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); +} + + +/* Coastal Dawn Theme - Soft, serene, golden accents */ +body.theme-coastal-dawn { + --primary-blue: #A9B4C2; + --warm-orange: #D4AF37; + --warm-amber: #C9A961; + --warm-yellow: #E6D088; + --soft-cream: #F8F7F4; + --text-dark: #333745; + --text-light: #6B7280; + --white: #FFFFFF; + --bg-light: #F8F7F4; + --border: #E5E7EB; + --gradient-warm: linear-gradient(135deg, #D4AF37 0%, #E6D088 100%); + --gradient-sunset: linear-gradient(135deg, #C9A961 0%, #D4AF37 50%, #E6D088 100%); + --gradient-ocean: linear-gradient(135deg, #A9B4C2 0%, #C5D3E0 100%); +} + +/* Deep Sea Theme - Dark, modern, electric blue accents */ +body.theme-deep-sea { + --primary-blue: #1E2022; + --warm-orange: #00BFFF; + --warm-amber: #1E90FF; + --warm-yellow: #4169E1; + --soft-cream: #2A2D30; + --text-dark: #E5E4E2; + --text-light: #C0C0C0; + --white: #1E2022; + --bg-light: #2A2D30; + --border: #3A3D40; + --gradient-warm: linear-gradient(135deg, #00BFFF 0%, #4169E1 100%); + --gradient-sunset: linear-gradient(135deg, #1E90FF 0%, #00BFFF 50%, #4169E1 100%); + --gradient-ocean: linear-gradient(135deg, #1E2022 0%, #2A2D30 100%); +} + +/* Monaco White Theme - Clean, minimalist, red accents */ +body.theme-monaco-white { + --primary-blue: #2C3E50; + --warm-orange: #E74C3C; + --warm-amber: #E67E22; + --warm-yellow: #F39C12; + --soft-cream: #F8F9FA; + --text-dark: #2C3E50; + --text-light: #7F8C8D; + --white: #FFFFFF; + --bg-light: #F8F9FA; + --border: #ECF0F1; + --gradient-warm: linear-gradient(135deg, #E74C3C 0%, #F39C12 100%); + --gradient-sunset: linear-gradient(135deg, #E67E22 0%, #E74C3C 50%, #F39C12 100%); + --gradient-ocean: linear-gradient(135deg, #2C3E50 0%, #34495E 100%); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* Global image fix to ensure images display */ +img { + max-width: 100%; + height: auto; + display: block; +} + +/* Lucide Icons Styling */ +[data-lucide] { + width: 24px; + height: 24px; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; + fill: none; + vertical-align: middle; +} + +.btn-icon { + width: 20px; + height: 20px; + display: inline-block; + margin-right: 8px; +} + +.spec-icon { + width: 20px; + height: 20px; + display: inline-block; + margin-right: 8px; + color: var(--warm-orange); +} + +.feature-icon i { + width: 32px; + height: 32px; + color: var(--warm-orange); +} + +.booking-icon i { + width: 48px; + height: 48px; + color: var(--warm-orange); + margin-bottom: 1rem; +} + +.footer-icon { + width: 16px; + height: 16px; + display: inline-block; + margin-right: 8px; + vertical-align: text-bottom; +} + +.social-links a { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.1); + transition: var(--transition); +} + +.social-links a:hover { + background: var(--warm-orange); +} + +.social-links a i { + width: 20px; + height: 20px; + color: white !important; + stroke: white !important; +} + +.social-links a svg { + stroke: white !important; + color: white !important; +} + +/* Star rating icons */ +.stars-icons { + display: inline-flex; + gap: 2px; +} + +.star-filled { + width: 16px; + height: 16px; + fill: var(--warm-yellow); + stroke: var(--warm-yellow); +} + +body { + font-family: var(--font-sans); + color: var(--text-dark); + background: var(--bg-light); + overflow-x: hidden; + line-height: 1.6; + transition: background 0.3s ease, color 0.3s ease; +} + +/* Smooth Scrolling */ +html { + scroll-behavior: smooth; +} + + +/* Navigation */ +.voyage-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + background: rgba(255, 255, 255, 0); + backdrop-filter: blur(0); + transition: var(--transition-slow); + padding: var(--space-md) 0; +} + +.voyage-nav.scrolled { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + padding: var(--space-sm) 0; +} + +.nav-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); + display: flex; + justify-content: space-between; + align-items: center; +} + +.nav-brand { + display: flex; + align-items: center; + gap: var(--space-sm); + font-family: var(--font-display); + font-size: 1.5rem; + font-weight: 700; + color: var(--white); + transition: var(--transition); +} + +.voyage-nav.scrolled .nav-brand { + color: var(--primary-blue); +} + +.nav-logo { + height: 50px; + width: auto; + object-fit: contain; +} + +.nav-links { + display: flex; + gap: var(--space-lg); + align-items: center; +} + +.nav-link { + color: var(--white); + text-decoration: none; + font-weight: 500; + transition: var(--transition); + position: relative; +} + +.voyage-nav.scrolled .nav-link { + color: var(--text-dark); +} + +.nav-link::after { + content: ''; + position: absolute; + bottom: -4px; + left: 0; + width: 0; + height: 2px; + background: var(--warm-orange); + transition: var(--transition); +} + +.nav-link:hover::after { + width: 100%; +} + +.nav-cta { + background: var(--gradient-warm); + color: var(--white) !important; + padding: 0.75rem 1.5rem; + border-radius: 50px; + transition: var(--transition); +} + +.nav-cta:hover { + transform: translateY(-2px); + box-shadow: 0 10px 20px rgba(249, 115, 22, 0.3); +} + +/* Theme Switcher */ +.theme-switcher { + position: relative; + margin-left: var(--space-md); +} + +.theme-btn { + background: transparent; + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: var(--transition); + color: var(--white); +} + +.voyage-nav.scrolled .theme-btn { + color: var(--primary-blue); + border-color: var(--primary-blue); +} + +.theme-btn:hover { + background: rgba(255, 255, 255, 0.1); + transform: rotate(180deg); +} + +.voyage-nav.scrolled .theme-btn:hover { + background: rgba(30, 58, 95, 0.1); +} + +.theme-dropdown { + position: absolute; + top: 50px; + right: 0; + background: var(--white); + border-radius: 12px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15); + padding: var(--space-sm); + min-width: 200px; + opacity: 0; + visibility: hidden; + transform: translateY(-10px); + transition: var(--transition); +} + +.theme-dropdown.active { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.theme-option { + display: flex; + align-items: center; + gap: var(--space-sm); + width: 100%; + padding: 0.75rem; + background: transparent; + border: none; + border-radius: 8px; + cursor: pointer; + transition: var(--transition); + text-align: left; +} + +.theme-option:hover { + background: var(--bg-light); +} + +.theme-colors { + display: flex; + gap: 2px; +} + +.theme-colors span { + width: 16px; + height: 16px; + border-radius: 50%; + border: 1px solid rgba(0, 0, 0, 0.1); +} + +/* Hero Section */ +.hero-voyage { + position: relative; + height: 100vh; + min-height: 600px; + overflow: hidden; +} + +.hero-video-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1; +} + +.hero-video { + width: 100%; + height: 100%; + object-fit: cover; +} + +.hero-image-fallback { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + z-index: -1; +} + +.hero-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 2; +} + +.gradient-warm { + background: linear-gradient(to bottom, + rgba(0, 31, 63, 0.3) 0%, + rgba(0, 31, 63, 0.5) 50%, + rgba(0, 31, 63, 0.7) 100%); +} + +.gradient-depth { + background: linear-gradient(to right, + rgba(220, 20, 60, 0.1) 0%, + transparent 100%); +} + +.hero-content { + position: relative; + z-index: 10; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + padding: var(--space-md); +} + +.trust-badge { + display: inline-flex; + align-items: center; + gap: var(--space-sm); + padding: 0.5rem 1.5rem; + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 50px; + color: var(--white) !important; + font-size: 0.875rem; + margin-bottom: var(--space-lg); + z-index: 10; + position: relative; +} + +/* Ensure the trust badge text is white */ +.trust-badge > span:last-child { + color: var(--white) !important; +} + +/* Make sure the headline is visible */ +.hero-headline { + display: block !important; + visibility: visible !important; +} + +.gradient-text { + display: inline-block !important; + visibility: visible !important; +} + +/* Make absolutely sure the trust badge text is white and visible */ +.trust-badge span:last-child { + display: inline !important; + color: white !important; + visibility: visible !important; +} + +.stars { + color: var(--warm-yellow); + font-size: 1rem; +} + +.hero-headline { + font-family: var(--font-display); + font-size: clamp(3rem, 8vw, 6rem); + font-weight: 900; + line-height: 1.1; + margin-bottom: var(--space-md); +} + +.gradient-text { + background: linear-gradient(135deg, + #ffffff 0%, + #f0f4f8 25%, + #e2e8f0 50%, + #cbd5e0 75%, + #94a3b8 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.hero-subtext { + font-size: 1.25rem; + color: rgba(255, 255, 255, 0.9); + max-width: 800px; + margin: 0 auto var(--space-lg); + font-weight: 300; + line-height: 1.8; +} + +.hero-actions { + display: flex; + gap: var(--space-md); + flex-wrap: wrap; + justify-content: center; +} + +.btn-primary-warm, +.btn-secondary-warm { + display: inline-flex; + align-items: center; + gap: var(--space-xs); + padding: 1rem 2rem; + font-size: 1.125rem; + font-weight: 600; + border: none; + border-radius: 50px; + cursor: pointer; + transition: var(--transition); + text-decoration: none; +} + +.btn-primary-warm { + background: var(--gradient-warm); + color: var(--white); + box-shadow: 0 10px 30px rgba(180, 139, 78, 0.3); +} + +.btn-primary-warm:hover { + transform: translateY(-3px); + box-shadow: 0 15px 40px rgba(180, 139, 78, 0.4); +} + +.btn-secondary-warm { + background: rgba(255, 255, 255, 0.1); + color: var(--white); + border: 2px solid rgba(255, 255, 255, 0.3); + backdrop-filter: blur(10px); +} + +.btn-secondary-warm:hover { + background: rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.5); + transform: translateY(-3px); +} + +.btn-icon { + font-size: 1.25rem; +} + +/* Scroll Indicator */ +.scroll-indicator { + position: absolute; + bottom: 2rem; + left: 50%; + transform: translateX(-50%); + color: rgba(255, 255, 255, 0.6); + font-size: 0.875rem; + animation: bounce 2s infinite; +} + +/* Updated scroll arrow styles */ +.scroll-arrow { + margin-top: 0.5rem; + display: flex; + justify-content: center; + align-items: center; +} + +.scroll-arrow i { + width: 32px; + height: 32px; + color: rgba(255, 255, 255, 0.6); + animation: arrow-bounce 1.5s infinite; +} + +@keyframes arrow-bounce { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(8px); + } +} + +/* Decorations */ +.hero-decoration { + position: absolute; + z-index: 5; +} + +.top-right { + top: 5rem; + right: 5rem; +} + +.bottom-left { + bottom: 8rem; + left: 5rem; +} + +.decoration-circle { + width: 80px; + height: 80px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 2rem; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.1); + animation: float 6s ease-in-out infinite; +} + +.decoration-circle.orange { + background: linear-gradient(135deg, + rgba(220, 20, 60, 0.2) 0%, + rgba(239, 68, 68, 0.2) 100%); +} + +.decoration-circle.blue { + background: linear-gradient(135deg, + rgba(0, 31, 63, 0.2) 0%, + rgba(0, 51, 102, 0.2) 100%); +} + +/* Welcome Section */ +.welcome-section { + padding: var(--space-2xl) 0; + background: linear-gradient(to bottom, var(--white) 0%, var(--bg-light) 100%); +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); +} + +.welcome-content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-2xl); + align-items: center; +} + +.section-title { + font-family: var(--font-display); + font-size: 3rem; + font-weight: 700; + margin-bottom: var(--space-md); + color: var(--primary-blue); +} + +.section-title.warm { + background: var(--gradient-warm); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.lead-text { + font-size: 1.25rem; + color: var(--text-light); + margin-bottom: var(--space-lg); + line-height: 1.8; +} + +.feature-list { + display: flex; + flex-direction: column; + gap: var(--space-md); +} + +.feature-item { + display: flex; + gap: var(--space-md); + align-items: flex-start; +} + +.feature-icon { + font-size: 2rem; + background: var(--gradient-warm); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.feature-item h4 { + font-size: 1.25rem; + margin-bottom: 0.25rem; + color: var(--text-dark); +} + +.feature-item p { + color: var(--text-light); +} + +.welcome-image { + position: relative; +} + +.rounded-image { + width: 100%; + border-radius: 20px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1); +} + +.image-badge { + position: absolute; + bottom: 2rem; + right: 2rem; + background: var(--gradient-warm); + color: var(--white); + padding: 1rem 1.5rem; + border-radius: 15px; + font-weight: 600; + display: flex; + flex-direction: column; + align-items: center; + box-shadow: 0 10px 30px rgba(180, 139, 78, 0.3); +} + +/* Fleet Showcase */ +.fleet-showcase { + padding: var(--space-2xl) 0; + background: var(--white); +} + +.section-header { + text-align: center; + margin-bottom: var(--space-2xl); +} + +.section-subtitle { + font-size: 1.25rem; + color: var(--text-light); + margin-top: var(--space-sm); +} + +.fleet-carousel { + position: relative; + max-width: 1000px; + margin: 0 auto; +} + +.yacht-card { + display: none; + grid-template-columns: 1fr 1fr; + gap: var(--space-lg); + align-items: center; + background: var(--white); + border-radius: 20px; + overflow: hidden; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08); +} + +.yacht-card.active { + display: grid; + animation: fadeIn 0.6s ease; +} + +.yacht-image-container { + position: relative; + height: 500px; + overflow: hidden; +} + +.yacht-image-container img { + width: 100%; + height: 100%; + object-fit: cover; + transition: var(--transition-slow); +} + +.yacht-card:hover .yacht-image-container img { + transform: scale(1.05); +} + +.yacht-badge { + position: absolute; + top: 2rem; + left: 2rem; + padding: 0.5rem 1rem; + border-radius: 50px; + font-weight: 600; + font-size: 0.875rem; + backdrop-filter: blur(10px); +} + +.yacht-badge.premium { + background: var(--gradient-warm); + color: var(--white); +} + +.yacht-badge.adventure { + background: var(--gradient-ocean); + color: var(--white); +} + +.yacht-details { + padding: var(--space-lg); +} + +.yacht-name { + font-family: var(--font-display); + font-size: 2.5rem; + margin-bottom: var(--space-sm); + color: var(--primary-blue); +} + +.yacht-description { + color: var(--text-light); + margin-bottom: var(--space-md); + line-height: 1.8; +} + +.yacht-specs { + display: flex; + flex-direction: column; + gap: var(--space-sm); + margin-bottom: var(--space-md); +} + +.spec { + display: flex; + align-items: center; + gap: var(--space-sm); +} + +.spec-icon { + font-size: 1.25rem; +} + +.yacht-pricing { + display: flex; + align-items: baseline; + gap: var(--space-xs); + margin-bottom: var(--space-md); +} + +.price-from { + color: var(--text-light); + font-size: 0.875rem; +} + +.price-amount { + font-size: 2rem; + font-weight: 700; + color: var(--warm-orange); +} + +.btn-book-yacht { + width: 100%; + padding: 1rem; + background: var(--gradient-warm); + color: var(--white); + border: none; + border-radius: 12px; + font-size: 1.125rem; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.btn-book-yacht:hover { + transform: translateY(-2px); + box-shadow: 0 10px 30px rgba(180, 139, 78, 0.3); +} + +.fleet-nav { + display: flex; + justify-content: center; + align-items: center; + gap: var(--space-md); + margin-top: var(--space-lg); +} + +.fleet-prev, +.fleet-next { + width: 50px; + height: 50px; + border-radius: 50%; + border: 2px solid var(--warm-orange); + background: var(--white); + color: var(--warm-orange); + font-size: 1.5rem; + cursor: pointer; + transition: var(--transition); +} + +.fleet-prev:hover, +.fleet-next:hover { + background: var(--gradient-warm); + color: var(--white); + border-color: transparent; +} + +.fleet-dots { + display: flex; + gap: var(--space-sm); +} + +.dot { + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--text-light); + opacity: 0.3; + cursor: pointer; + transition: var(--transition); +} + +.dot.active { + background: var(--warm-orange); + opacity: 1; + transform: scale(1.2); +} + +/* Services Section */ +.services-section { + padding: var(--space-2xl) 0; + background: linear-gradient(135deg, var(--soft-cream) 0%, var(--white) 100%); +} + +.services-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: var(--space-lg); + margin: var(--space-xl) 0; +} + +.service-card { + background: var(--white); + border-radius: 20px; + padding: var(--space-xl); + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08); + transition: var(--transition); + position: relative; + overflow: hidden; +} + +.service-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 5px; + background: var(--gradient-warm); +} + +.charter-service::before { + background: var(--gradient-ocean); +} + +.service-card:hover { + transform: translateY(-5px); + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.12); +} + +.service-icon-wrapper { + width: 80px; + height: 80px; + background: var(--gradient-warm); + border-radius: 20px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: var(--space-md); +} + +.charter-service .service-icon-wrapper { + background: var(--gradient-ocean); +} + +.service-icon { + width: 40px; + height: 40px; + color: white; +} + +.service-card h3 { + font-family: var(--font-display); + font-size: 2rem; + margin-bottom: var(--space-sm); + color: var(--text-dark); +} + +.service-card p { + color: var(--text-light); + margin-bottom: var(--space-md); + line-height: 1.6; +} + +.service-features { + list-style: none; + padding: 0; + margin: var(--space-md) 0; +} + +.service-features li { + padding: 0.5rem 0; + display: flex; + align-items: center; + color: var(--text-dark); +} + +.check-icon { + width: 20px; + height: 20px; + color: var(--warm-orange); + margin-right: 0.75rem; + flex-shrink: 0; +} + +.btn-service { + width: 100%; + padding: 1rem 2rem; + background: var(--gradient-warm); + color: white; + border: none; + border-radius: 12px; + font-weight: 600; + font-size: 1rem; + cursor: pointer; + transition: var(--transition); + margin-top: var(--space-md); +} + +.charter-service .btn-service { + background: var(--gradient-ocean); +} + +.btn-service:hover { + transform: scale(1.02); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); +} + +/* Service Stats */ +.service-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--space-md); + margin-top: var(--space-xl); + padding: var(--space-lg); + background: white; + border-radius: 20px; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05); +} + +.stat-item { + text-align: center; + padding: var(--space-md); +} + +.stat-icon { + width: 40px; + height: 40px; + color: var(--warm-orange); + margin-bottom: var(--space-sm); +} + +.stat-number { + display: block; + font-size: 2.5rem; + font-weight: 800; + color: var(--primary-blue); + font-family: var(--font-display); +} + +.stat-label { + display: block; + color: var(--text-light); + font-size: 0.875rem; + margin-top: 0.5rem; +} + +/* Experience Stories */ +.experience-stories { + padding: var(--space-2xl) 0; + background: var(--bg-light); +} + +.story-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); +} + +.section-title.center { + text-align: center; + margin-bottom: var(--space-2xl); +} + +.stories-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: var(--space-lg); +} + +.story-card { + background: var(--white); + border-radius: 20px; + overflow: hidden; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); + transition: var(--transition); +} + +.story-card:hover { + transform: translateY(-5px); + box-shadow: 0 15px 40px rgba(0, 0, 0, 0.12); +} + +.story-image { + position: relative; + height: 250px; + overflow: hidden; +} + +.story-image img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.story-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(to bottom, transparent 0%, rgba(0, 0, 0, 0.5) 100%); + display: flex; + align-items: flex-end; + padding: var(--space-md); +} + +.story-category { + background: var(--gradient-warm); + color: var(--white); + padding: 0.5rem 1rem; + border-radius: 50px; + font-size: 0.875rem; + font-weight: 600; +} + +.story-content { + padding: var(--space-md); +} + +.story-content h3 { + font-family: var(--font-display); + font-size: 1.5rem; + margin-bottom: var(--space-sm); + color: var(--text-dark); +} + +.story-content p { + color: var(--text-light); + line-height: 1.6; + margin-bottom: var(--space-sm); +} + +.story-link { + color: var(--warm-orange); + text-decoration: none; + font-weight: 600; + transition: var(--transition); +} + +.story-link:hover { + color: var(--warm-amber); +} + +/* Gallery Section */ +.gallery-section { + padding: var(--space-2xl) 0; + background: var(--white); +} + +.image-gallery { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--space-md); + margin-top: var(--space-lg); +} + +.gallery-item { + position: relative; + border-radius: 15px; + overflow: hidden; + height: 300px; + cursor: pointer; + transition: var(--transition); +} + +.gallery-item.large { + grid-column: span 2; + height: 400px; +} + +.gallery-item img { + width: 100%; + height: 100%; + object-fit: cover; + transition: var(--transition-slow); +} + +.gallery-overlay { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: linear-gradient(to top, rgba(0, 31, 63, 0.9) 0%, transparent 100%); + padding: var(--space-lg) var(--space-md) var(--space-md); + transform: translateY(100%); + transition: var(--transition); +} + +.gallery-caption { + color: var(--white); + font-size: 1.25rem; + font-weight: 600; +} + +.gallery-item:hover .gallery-overlay { + transform: translateY(0); +} + +.gallery-item:hover img { + transform: scale(1.1); +} + +@media (max-width: 768px) { + .gallery-item.large { + grid-column: span 1; + height: 300px; + } +} + +/* Booking CTA */ +.booking-cta { + padding: var(--space-2xl) 0; + background: linear-gradient(135deg, #001f3f 0%, #003366 100%); +} + +.booking-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); +} + +.booking-content { + text-align: center; +} + +.booking-title { + font-family: var(--font-display); + font-size: 3rem; + color: var(--white); + margin-bottom: var(--space-sm); +} + +.booking-subtitle { + font-size: 1.25rem; + color: rgba(255, 255, 255, 0.8); + margin-bottom: var(--space-2xl); +} + +.booking-options { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--space-lg); +} + +.booking-card { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 20px; + padding: var(--space-lg); + text-align: center; + transition: var(--transition); +} + +.booking-card:hover { + background: rgba(255, 255, 255, 0.15); + transform: translateY(-5px); +} + +.booking-card.featured { + background: var(--gradient-warm); + border: none; + transform: scale(1.05); +} + +.booking-icon { + font-size: 3rem; + margin-bottom: var(--space-sm); +} + +.booking-card h3 { + font-size: 1.5rem; + color: var(--white); + margin-bottom: var(--space-xs); +} + +.booking-card p { + color: rgba(255, 255, 255, 0.8); + margin-bottom: var(--space-md); +} + +.btn-booking { + width: 100%; + padding: 0.75rem 1.5rem; + background: rgba(255, 255, 255, 0.2); + color: var(--white); + border: 2px solid var(--white); + border-radius: 50px; + font-weight: 600; + cursor: pointer; + transition: var(--transition); +} + +.btn-booking:hover { + background: var(--white); + color: var(--primary-blue); +} + +.btn-booking.primary { + background: var(--white); + color: var(--warm-orange); + border-color: var(--white); +} + +.btn-booking.primary:hover { + background: var(--gradient-warm); + color: var(--white); +} + +/* Footer */ +.voyage-footer { + background: var(--primary-blue); + color: var(--white); + padding: var(--space-2xl) 0 var(--space-md); +} + +.footer-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-md); +} + +.footer-content { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + gap: var(--space-2xl); + margin-bottom: var(--space-lg); +} + +.footer-brand h3 { + font-family: var(--font-display); + font-size: 2rem; + margin-bottom: var(--space-sm); +} + +.footer-logo { + width: 50px; + height: 50px; + border-radius: 50%; + margin-bottom: var(--space-sm); +} + +.footer-brand p { + color: rgba(255, 255, 255, 0.7); + margin-bottom: var(--space-md); +} + +.social-links { + display: flex; + gap: var(--space-sm); +} + +.social-links a { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.1); + border-radius: 50%; + font-size: 1.25rem; + transition: var(--transition); +} + +.social-links a:hover { + background: var(--gradient-warm); + transform: translateY(-3px); +} + +.footer-links h4, +.footer-contact h4 { + margin-bottom: var(--space-md); +} + +/* Force social icons to be white */ +.voyage-footer .social-links a svg, +.voyage-footer .social-links a i { + stroke: white !important; + color: white !important; + fill: none !important; +} + +.footer-links a { + display: block; + color: rgba(255, 255, 255, 0.7); + text-decoration: none; + margin-bottom: var(--space-xs); + transition: var(--transition); +} + +.footer-links a:hover { + color: var(--warm-orange); +} + +.footer-contact p { + color: rgba(255, 255, 255, 0.7); + margin-bottom: var(--space-xs); +} + +.footer-bottom { + text-align: center; + padding-top: var(--space-lg); + border-top: 1px solid rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.5); +} + +/* Layout Switcher */ +.layout-switcher { + position: fixed; + bottom: 2rem; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 0.5rem; + background: var(--white); + padding: 0.5rem; + border-radius: 50px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); + z-index: 90; +} + +.layout-btn { + padding: 0.5rem 1rem; + background: transparent; + border: none; + border-radius: 50px; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: var(--transition); + color: var(--text-dark); +} + +.layout-btn:hover { + background: var(--bg-light); +} + +.layout-btn.active { + background: var(--gradient-warm); + color: var(--white); +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { + transform: translateX(-50%) translateY(0); + } + 40% { + transform: translateX(-50%) translateY(-10px); + } + 60% { + transform: translateX(-50%) translateY(-5px); + } +} + +@keyframes scroll { + 0% { + top: 8px; + opacity: 1; + } + 100% { + top: 20px; + opacity: 0; + } +} + +@keyframes float { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-20px); + } +} + +.animate-fade-in { + animation: fadeIn 0.8s ease; +} + +.animate-fade-up { + animation: fadeIn 0.8s ease 0.2s both; +} + +.animate-fade-up-delay { + animation: fadeIn 0.8s ease 0.4s both; +} + +.animate-fade-up-delay-2 { + animation: fadeIn 0.8s ease 0.6s both; +} + +/* Responsive */ +@media (max-width: 768px) { + .hero-headline { + font-size: 3rem; + } + + .hero-subtext { + font-size: 1rem; + } + + .hero-actions { + flex-direction: column; + width: 100%; + padding: 0 var(--space-md); + } + + .btn-primary-warm, + .btn-secondary-warm { + width: 100%; + } + + .welcome-content { + grid-template-columns: 1fr; + } + + .yacht-card { + grid-template-columns: 1fr; + } + + .footer-content { + grid-template-columns: 1fr; + text-align: center; + } + + .social-links { + justify-content: center; + } + + .top-right, + .bottom-left { + display: none; + } + + .nav-links { + display: none; + } +} + +/* Dark theme adjustments for Deep Sea */ +body.theme-deep-sea .welcome-section, +body.theme-deep-sea .fleet-showcase, +body.theme-deep-sea .experience-stories, +body.theme-deep-sea .booking-cta { + background: var(--bg-light); +} + +/* Gold theme is now the default - no overrides needed */ + +body.theme-deep-sea .yacht-card, +body.theme-deep-sea .story-card, +body.theme-deep-sea .booking-card { + background: var(--soft-cream); + color: var(--text-dark); +} + +body.theme-deep-sea .hero-content h1, +body.theme-deep-sea .hero-content p { + color: var(--text-dark); +} + +body.theme-deep-sea .voyage-nav:not(.scrolled) .nav-brand, +body.theme-deep-sea .voyage-nav:not(.scrolled) .nav-link { + color: var(--text-dark); +} + +body.theme-deep-sea .voyage-footer { + background: var(--primary-blue); + color: var(--text-dark); +} + +/* Theme-specific button colors */ +body.theme-coastal-dawn .btn-primary-warm, +body.theme-coastal-dawn .btn-secondary-warm { + background: var(--warm-orange); + color: var(--white); +} + +body.theme-deep-sea .btn-primary-warm, +body.theme-deep-sea .btn-secondary-warm { + background: var(--warm-orange); + color: var(--white); +} + +body.theme-monaco-white .btn-primary-warm, +body.theme-monaco-white .btn-secondary-warm { + background: var(--warm-orange); + color: var(--white); +} \ No newline at end of file diff --git a/website-mockups/diver.jpg b/website-mockups/diver.jpg new file mode 100644 index 0000000..c87c76c Binary files /dev/null and b/website-mockups/diver.jpg differ diff --git a/website-mockups/diver_cleaning.jpg b/website-mockups/diver_cleaning.jpg new file mode 100644 index 0000000..e6b651c Binary files /dev/null and b/website-mockups/diver_cleaning.jpg differ diff --git a/website-mockups/diver_cleaning_2.jpg b/website-mockups/diver_cleaning_2.jpg new file mode 100644 index 0000000..2ea5176 Binary files /dev/null and b/website-mockups/diver_cleaning_2.jpg differ diff --git a/website-mockups/faq.html b/website-mockups/faq.html new file mode 100644 index 0000000..15b5826 --- /dev/null +++ b/website-mockups/faq.html @@ -0,0 +1,872 @@ + + + + + + Frequently Asked Questions - HarborSmith + + + + + + + + + + + + + +
+
+

Frequently Asked Questions

+

Find answers to common questions about our yacht charters and maintenance services

+ +
+
+ + +
+
+ +
+ + + + + +
+ + +
+ +
+ +
+
+ What is included in a yacht charter? + + + +
+
+
+ Our yacht charters include: +
    +
  • Fully fueled yacht for your charter duration
  • +
  • Licensed and experienced captain
  • +
  • Safety equipment and life jackets for all passengers
  • +
  • Basic amenities (water, ice, coolers)
  • +
  • Bluetooth sound system
  • +
  • Complimentary parking at our marina
  • +
+ Additional services like catering, photographer, and water sports equipment can be added for an extra fee. +
+
+
+ +
+
+ How far in advance should I book? + + + +
+
+
+ We recommend booking at least 2-4 weeks in advance for weekend charters and 1-2 weeks for weekday charters. During peak season (May through October), booking 4-6 weeks ahead is advisable. For major holidays and special events like Fleet Week, we suggest booking 2-3 months in advance to secure your preferred yacht and time. +
+
+
+ +
+
+ What is your cancellation policy? + + + +
+
+
+ Our cancellation policy is designed to be fair and flexible: +
    +
  • 48+ hours before charter: Full refund
  • +
  • 24-48 hours before: 50% refund or full credit for rescheduling
  • +
  • Less than 24 hours: No refund, but 50% credit toward future charter
  • +
  • Weather cancellations: Full refund or rescheduling at no charge
  • +
+ We understand plans change, so we try to work with our customers whenever possible. +
+
+
+ +
+
+ Do I need boating experience to charter a yacht? + + + +
+
+
+ No boating experience is required! All of our charters come with a licensed, experienced captain who will handle all navigation and sailing. You and your guests can simply relax and enjoy the experience. If you're interested in learning, our captains are happy to share their knowledge and even let you take the helm under supervision. +
+
+
+ +
+
+ Can we bring our own food and drinks? + + + +
+
+
+ Yes, absolutely! You're welcome to bring your own food and beverages, including alcohol (for passengers 21+). We provide coolers with ice, and our yachts have refrigeration available. We also offer catering packages if you prefer to have everything prepared for you, ranging from light snacks to full gourmet meals. +
+
+
+ +
+
+ What happens if the weather is bad? + + + +
+
+
+ Safety is our top priority. If weather conditions are unsafe for sailing (high winds, storms, heavy fog), we will: +
    +
  • Contact you as early as possible (usually the day before)
  • +
  • Offer to reschedule to another available date
  • +
  • Provide a full refund if rescheduling isn't possible
  • +
+ Light rain or overcast conditions typically don't affect charters, and many guests find sailing in light mist quite romantic! +
+
+
+ +
+
+ How often should I service my yacht? + + + +
+
+
+ Regular maintenance schedules depend on usage, but we recommend: +
    +
  • Oil & filter changes: Every 100 hours or annually
  • +
  • Bottom cleaning: Every 1-3 months depending on water conditions
  • +
  • Engine service: Every 100 hours or annually
  • +
  • Hull inspection: Every 6 months
  • +
  • Complete service: Annually before peak season
  • +
+ We offer customized maintenance plans based on your yacht's specific needs and usage patterns. +
+
+
+ +
+
+ Do you work on all yacht brands? + + + +
+
+
+ Yes! Our certified technicians are trained to work on all major yacht and engine brands including: +
    +
  • Mercury, Yamaha, Volvo Penta, Caterpillar engines
  • +
  • Beneteau, Jeanneau, Catalina, Hunter sailboats
  • +
  • Sea Ray, Bayliner, Carver, Azimut motor yachts
  • +
  • And many more...
  • +
+ We have specialized certifications for Mercury, Yamaha, and Volvo marine systems. +
+
+
+ +
+
+ How much does maintenance cost? + + + +
+
+
+ Maintenance costs vary based on yacht size and services needed. Here are typical ranges: +
    +
  • Basic maintenance package: From $299/month
  • +
  • Oil change: $200-400
  • +
  • Hull cleaning: $200-500
  • +
  • Engine service: $450-1,200
  • +
  • Bottom paint: $1,500-4,000
  • +
+ We provide free inspections and detailed quotes before any work begins. No surprises! +
+
+
+ +
+
+ Are children allowed on charters? + + + +
+
+
+ Yes, we love having families aboard! Children of all ages are welcome. We provide: +
    +
  • Child-sized life jackets (required for all children under 13)
  • +
  • Safety briefing appropriate for young passengers
  • +
  • Family-friendly routes with calmer waters
  • +
  • Recommendations for kid-friendly activities
  • +
+ Children under 18 must be accompanied by a parent or guardian. We recommend our morning or early afternoon charters for families with young children. +
+
+
+ +
+
+ What forms of payment do you accept? + + + +
+
+
+ We accept multiple payment methods for your convenience: +
    +
  • All major credit cards (Visa, Mastercard, Amex, Discover)
  • +
  • Debit cards
  • +
  • PayPal and Venmo
  • +
  • Wire transfers for large bookings
  • +
  • Company checks (with prior approval)
  • +
+ Full payment is required to confirm charter bookings. For maintenance services over $1,000, we offer payment plans. +
+
+
+ +
+
+ What are the best routes for first-time visitors? + + + +
+
+
+ For first-time visitors, we highly recommend: +
    +
  • Golden Gate Tour: Sail under the iconic bridge, around Alcatraz, with views of the city skyline
  • +
  • Sunset Cruise: Perfect timing for golden hour photos with all major landmarks
  • +
  • Bay Complete: Comprehensive 4-hour tour covering all highlights
  • +
+ Our captains will customize the route based on weather conditions and your interests. Don't forget your camera! +
+
+
+ +
+
+ Do you offer emergency repair services? + + + +
+
+
+ Yes, we offer 24/7 emergency services! Call our emergency line at (415) 555-0911 for: +
    +
  • Engine failures
  • +
  • Electrical emergencies
  • +
  • Taking on water
  • +
  • Towing services
  • +
  • On-water repairs
  • +
+ Our emergency response team can reach most Bay Area locations within 45 minutes. Emergency rates apply, but your safety is our priority. +
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ + + + + +
+ +
+ + + + \ No newline at end of file diff --git a/website-mockups/golden_gate.jpg b/website-mockups/golden_gate.jpg new file mode 100644 index 0000000..ec49660 Binary files /dev/null and b/website-mockups/golden_gate.jpg differ diff --git a/website-mockups/iStock-2189089654.jpg b/website-mockups/iStock-2189089654.jpg new file mode 100644 index 0000000..eba7d06 Binary files /dev/null and b/website-mockups/iStock-2189089654.jpg differ diff --git a/website-mockups/iStock-504868014.jpg b/website-mockups/iStock-504868014.jpg new file mode 100644 index 0000000..0d4a757 Binary files /dev/null and b/website-mockups/iStock-504868014.jpg differ diff --git a/website-mockups/iStock-923244752.jpg b/website-mockups/iStock-923244752.jpg new file mode 100644 index 0000000..c24a2a2 Binary files /dev/null and b/website-mockups/iStock-923244752.jpg differ diff --git a/website-mockups/iStock-923244752b.jpg b/website-mockups/iStock-923244752b.jpg new file mode 100644 index 0000000..e42bf1d Binary files /dev/null and b/website-mockups/iStock-923244752b.jpg differ diff --git a/website-mockups/index-backup-20250918-135142.html b/website-mockups/index-backup-20250918-135142.html new file mode 100644 index 0000000..3e7d2f7 --- /dev/null +++ b/website-mockups/index-backup-20250918-135142.html @@ -0,0 +1,584 @@ + + + + + + HarborSmith - Your Adventure Awaits + + + + + + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + +
+ +
+
+ + + + + + + +
+ Trusted by 0+ adventurers +
+ + +

+ Your Adventure Awaits +

+ + +

+ Create unforgettable memories sailing through the stunning San Francisco Bay.
+ Experience the freedom of the open water with our premium yacht charters designed for every occasion. +

+ + +
+ + +
+ + +
+ Scroll to explore +
+ +
+
+
+ +
+ + +
+
+
+
+

Welcome Aboard!

+

+ We're not just a yacht charter service โ€“ we're your gateway to extraordinary moments on the water. + Our family-owned business has been creating magical experiences in San Francisco Bay for over 15 years. +

+
+
+ + + +
+

Local Expertise

+

Our captains know every hidden cove and perfect sunset spot

+
+
+
+ + + +
+

Family Friendly

+

Safe, fun experiences for all ages and skill levels

+
+
+
+ + + +
+

Tailored to You

+

Every charter is customized to create your perfect day

+
+
+
+
+
+ Happy customers on yacht +
+ 15+ Years + of Excellence +
+
+
+
+
+ + +
+
+
+

Meet Your Perfect Yacht

+

Each vessel in our fleet offers a unique experience

+
+ + +
+
+ + +
+
+
+

Complete Yacht Services

+

From unforgettable charters to expert maintenance

+
+ +
+ +
+
+ +
+

Premium Charters

+

Experience the Bay like never before with our luxury yacht charters.

+
    +
  • Private celebrations
  • +
  • Corporate events
  • +
  • Romantic cruises
  • +
  • Family adventures
  • +
+ +
+ + +
+
+ +
+

Expert Maintenance

+

Keep your vessel in pristine condition with our professional services.

+
    +
  • Regular maintenance
  • +
  • Engine services
  • +
  • Hull cleaning
  • +
  • Emergency repairs
  • +
+ +
+ + +
+
+ +
+

Client Portal

+

Access your personalized dashboard for bookings and services.

+
    +
  • Manage bookings
  • +
  • Service history
  • +
  • Pay invoices
  • +
  • View documents
  • +
+ +
+
+ + +
+
+ + 50+ + Vessels Maintained +
+
+ + 15+ + Years Experience +
+
+ + 500+ + Happy Clients +
+
+ + 100% + Certified Technicians +
+
+
+
+ + +
+
+

Stories from the Bay

+ +
+ +
+
+ Romantic sunset +
+ +
+
+
+

The Perfect Proposal

+

"He proposed as the sun set behind the Golden Gate. The crew was so discrete and helpful. + It was absolutely magical!" - Sarah & Mike

+ Read their story โ†’ +
+
+ + +
+
+ Team building +
+ +
+
+
+

Team Building Success

+

"Our team had an incredible day on the water. It brought everyone together in a way + no conference room ever could." - TechStart Inc.

+ Read their story โ†’ +
+
+ + +
+
+ Family adventure +
+ +
+
+
+

Three Generations at Sea

+

"From grandma to the kids, everyone had a blast. The crew made sure everyone felt safe + and included." - The Johnson Family

+ Read their story โ†’ +
+
+
+
+
+ + + + + +
+
+
+

Ready to Create Your Story?

+

+ Join hundreds of happy adventurers who've discovered the magic of San Francisco Bay +

+ +
+
+ + + +

Plan Your Charter

+

Choose your perfect date and yacht

+ +
+ + + +
+ + + +

Gift a Charter

+

Give the gift of adventure

+ +
+
+
+
+
+ + +
+ +
+ + + + + + + \ No newline at end of file diff --git a/website-mockups/index.html b/website-mockups/index.html new file mode 100644 index 0000000..e30c73f --- /dev/null +++ b/website-mockups/index.html @@ -0,0 +1,464 @@ + + + + + + Harbor Smith - Personalized Service Maintenance For Your Boat + + + + + + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + +
+ + + + +
+
+ + + + + + + +
+ Trusted by 0+ seafarers +
+ + +

+ + Personalized Service Maintenance for Your Boat + + Keep your vessel pristine with San Francisco Bay's premier mobile boat maintenance service. +

+ + +
+ + +
+ + +
+ Scroll to explore +
+ +
+
+
+ +
+ + +
+
+
+
+

Why Choose Harbor Smith?

+

+ We're the San Francisco Bay Area's premier mobile boat maintenance service. + Our professional team brings expert care directly to your dock, ensuring your vessel stays in pristine condition year-round. +

+
+
+ + + +
+

Mobile Service

+

We come to you - convenient service at your dock or marina

+
+
+
+ + + +
+

Certified Professionals

+

Experienced technicians with marine industry certifications

+
+
+
+ + + +
+

Reliable & Consistent

+

Regular maintenance schedules tailored to your needs

+
+
+
+
+
+ Harbor Smith team member +
+ 10+ Years + of Excellence +
+
+
+
+
+ + +
+
+
+

Our Premium Services

+

Professional boat maintenance tailored to your needs

+
+ +
+ +
+
+ Professional hull cleaning service +
+
+

Hull Cleaning

+

+ Professional underwater hull cleaning to maintain your boat's performance and fuel efficiency. +

+
    +
  • Removes marine growth
  • +
  • Improves fuel efficiency
  • +
  • Extends hull life
  • +
+ +
+
+ + +
+
+ Professional boat wash and wax service +
+
+

Exterior Wash & Wax

+

+ Complete exterior detailing to keep your boat looking pristine and protected. +

+
    +
  • Deep cleaning wash
  • +
  • UV protection wax
  • +
  • Gel coat restoration
  • +
+ +
+
+ + +
+
+ Zinc anode replacement service +
+
+

Anode Changes

+

+ Essential corrosion protection with regular zinc anode inspection and replacement. +

+
    +
  • Prevents corrosion
  • +
  • Regular inspection
  • +
  • Marine-grade materials
  • +
+ +
+
+ + +
+
+ Professional interior detailing service +
+
+

Interior Detailing

+

+ Thorough interior cleaning and conditioning for a fresh, comfortable cabin. +

+
    +
  • Upholstery cleaning
  • +
  • Mold & mildew treatment
  • +
  • Surface conditioning
  • +
+ +
+
+
+
+
+ + +
+
+
+
+ + 200+ + Vessels Maintained +
+
+ + 10+ + Years Experience +
+
+ + 500+ + Happy Clients +
+
+ + 100% + Mobile Service +
+
+
+
+ + +
+
+

What Our Customers Say

+ +
+
+ + + + + +
+
+ "They do an amazing job and are always reliable! I never have to worry about my boat's condition." +
+ โ€” John D. +
+ +
+ +
+
+
+ + + + + +
+

Professional Service

+

"Harbor Smith keeps my boat in pristine condition. Their attention to detail is unmatched."

+ โ€” Michael R. +
+
+ +
+
+
+ + + + + +
+

Convenient & Reliable

+

"Mobile service that comes to my dock - it doesn't get better than that! Highly recommended."

+ โ€” Sarah L. +
+
+ +
+
+
+ + + + + +
+

Excellent Value

+

"Fair pricing and exceptional quality. They've been maintaining my yacht for 5 years now."

+ โ€” David K. +
+
+
+
+
+ + + + + +
+
+
+

Ready to Schedule Your Service?

+

+ Join hundreds of boat owners who trust Harbor Smith for professional maintenance +

+ +
+
+ + + +

Schedule Service

+

Book your maintenance appointment

+ +
+ + + +
+ + + +

Email Us

+

Send us your service request

+ +
+
+
+
+
+ + +
+ +
+ + + + + + + \ No newline at end of file diff --git a/website-mockups/js-backup-20250918-135225/animations.js b/website-mockups/js-backup-20250918-135225/animations.js new file mode 100644 index 0000000..27f355e --- /dev/null +++ b/website-mockups/js-backup-20250918-135225/animations.js @@ -0,0 +1,349 @@ +// HarborSmith - Animation Scripts +// ================================ + +// Scroll Animation Observer +const scrollAnimations = { + init() { + this.observeElements(); + this.initProgressBars(); + this.initTextAnimations(); + }, + + observeElements() { + const options = { + threshold: 0.1, + rootMargin: '0px 0px -100px 0px' + }; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('visible'); + + // Trigger specific animations + if (entry.target.classList.contains('counter-animate')) { + this.animateValue(entry.target); + } + + // Only animate once + if (!entry.target.classList.contains('repeat-animation')) { + observer.unobserve(entry.target); + } + } + }); + }, options); + + // Observe all animated elements + const animatedElements = document.querySelectorAll('.scroll-animate, .scroll-animate-left, .scroll-animate-right, .scroll-animate-scale, .counter-animate'); + animatedElements.forEach(el => observer.observe(el)); + }, + + animateValue(element) { + const target = parseInt(element.dataset.target); + const duration = parseInt(element.dataset.duration) || 2000; + const start = 0; + const increment = target / (duration / 16); + let current = start; + + const updateValue = () => { + current += increment; + if (current < target) { + element.textContent = Math.floor(current); + requestAnimationFrame(updateValue); + } else { + element.textContent = target; + } + }; + + updateValue(); + }, + + initProgressBars() { + const progressBars = document.querySelectorAll('.progress-bar-fill'); + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const progressBar = entry.target; + const targetWidth = progressBar.dataset.progress || '100'; + progressBar.style.setProperty('--progress', targetWidth + '%'); + progressBar.classList.add('animate'); + observer.unobserve(progressBar); + } + }); + }, { threshold: 0.5 }); + + progressBars.forEach(bar => observer.observe(bar)); + }, + + initTextAnimations() { + const textElements = document.querySelectorAll('.text-animate'); + + textElements.forEach(element => { + const text = element.textContent; + element.textContent = ''; + + [...text].forEach((char, index) => { + const span = document.createElement('span'); + span.textContent = char === ' ' ? '\u00A0' : char; + span.style.animationDelay = `${index * 0.05}s`; + span.classList.add('char-animate'); + element.appendChild(span); + }); + }); + } +}; + +// Cursor Effects +const cursorEffects = { + init() { + this.cursor = document.createElement('div'); + this.cursor.className = 'custom-cursor'; + document.body.appendChild(this.cursor); + + this.follower = document.createElement('div'); + this.follower.className = 'cursor-follower'; + document.body.appendChild(this.follower); + + this.initEventListeners(); + }, + + initEventListeners() { + document.addEventListener('mousemove', (e) => { + this.cursor.style.left = e.clientX + 'px'; + this.cursor.style.top = e.clientY + 'px'; + + setTimeout(() => { + this.follower.style.left = e.clientX + 'px'; + this.follower.style.top = e.clientY + 'px'; + }, 100); + }); + + // Add hover effects + const interactiveElements = document.querySelectorAll('a, button, .clickable'); + interactiveElements.forEach(el => { + el.addEventListener('mouseenter', () => { + this.cursor.classList.add('hover'); + this.follower.classList.add('hover'); + }); + + el.addEventListener('mouseleave', () => { + this.cursor.classList.remove('hover'); + this.follower.classList.remove('hover'); + }); + }); + } +}; + +// Ripple Effect +const rippleEffect = { + init() { + const buttons = document.querySelectorAll('.btn, .ripple'); + + buttons.forEach(button => { + button.addEventListener('click', (e) => { + const ripple = document.createElement('span'); + ripple.className = 'ripple-effect'; + + const rect = button.getBoundingClientRect(); + const size = Math.max(rect.width, rect.height); + const x = e.clientX - rect.left - size / 2; + const y = e.clientY - rect.top - size / 2; + + ripple.style.width = ripple.style.height = size + 'px'; + ripple.style.left = x + 'px'; + ripple.style.top = y + 'px'; + + button.appendChild(ripple); + + setTimeout(() => { + ripple.remove(); + }, 600); + }); + }); + } +}; + +// Magnetic Buttons +const magneticButtons = { + init() { + const magneticElements = document.querySelectorAll('.magnetic'); + + magneticElements.forEach(element => { + element.addEventListener('mousemove', (e) => { + const rect = element.getBoundingClientRect(); + const x = e.clientX - rect.left - rect.width / 2; + const y = e.clientY - rect.top - rect.height / 2; + + element.style.transform = `translate(${x * 0.2}px, ${y * 0.2}px)`; + }); + + element.addEventListener('mouseleave', () => { + element.style.transform = 'translate(0, 0)'; + }); + }); + } +}; + +// Page Loader +const pageLoader = { + init() { + const loader = document.createElement('div'); + loader.className = 'page-loader'; + loader.innerHTML = ` +
+
+ + + + + +
+
Setting Sail...
+
+ `; + + document.body.appendChild(loader); + + window.addEventListener('load', () => { + setTimeout(() => { + loader.classList.add('fade-out'); + setTimeout(() => { + loader.remove(); + }, 500); + }, 1000); + }); + } +}; + +// Smooth Page Transitions +const pageTransitions = { + init() { + const links = document.querySelectorAll('a[href^="/"], a[href^="./"], a[href$=".html"]'); + + links.forEach(link => { + link.addEventListener('click', (e) => { + const href = link.getAttribute('href'); + + // Skip if it's an anchor link or external link + if (href.startsWith('#') || href.startsWith('http')) return; + + e.preventDefault(); + + document.body.classList.add('page-transition'); + + setTimeout(() => { + window.location.href = href; + }, 500); + }); + }); + } +}; + +// Initialize animations +document.addEventListener('DOMContentLoaded', () => { + scrollAnimations.init(); + rippleEffect.init(); + magneticButtons.init(); + // Uncomment these for production + // cursorEffects.init(); + // pageLoader.init(); + // pageTransitions.init(); +}); + +// Add some CSS for the loader (normally would be in a separate file) +const loaderStyles = ` + +`; + +// Inject loader styles +document.head.insertAdjacentHTML('beforeend', loaderStyles); \ No newline at end of file diff --git a/website-mockups/js-backup-20250918-135225/bento-layout.js b/website-mockups/js-backup-20250918-135225/bento-layout.js new file mode 100644 index 0000000..120caf7 --- /dev/null +++ b/website-mockups/js-backup-20250918-135225/bento-layout.js @@ -0,0 +1,375 @@ +// Bento Grid Layout JavaScript + +document.addEventListener('DOMContentLoaded', function() { + // Menu Toggle + const menuToggle = document.getElementById('menuToggle'); + const menuOverlay = document.getElementById('menuOverlay'); + const menuClose = document.getElementById('menuClose'); + + menuToggle.addEventListener('click', () => { + menuOverlay.classList.add('active'); + }); + + menuClose.addEventListener('click', () => { + menuOverlay.classList.remove('active'); + }); + + // Filter Toggle + const filterToggle = document.getElementById('filterToggle'); + const filterBar = document.getElementById('filterBar'); + + filterToggle.addEventListener('click', () => { + filterBar.classList.toggle('active'); + }); + + // Filter Functionality + const filterChips = document.querySelectorAll('.filter-chip'); + const bentoTiles = document.querySelectorAll('.bento-tile'); + + filterChips.forEach(chip => { + chip.addEventListener('click', () => { + // Update active state + filterChips.forEach(c => c.classList.remove('active')); + chip.classList.add('active'); + + const filter = chip.dataset.filter; + + // Filter tiles + bentoTiles.forEach(tile => { + if (filter === 'all' || tile.dataset.category === filter) { + tile.classList.remove('hiding'); + tile.classList.add('showing'); + } else { + tile.classList.add('hiding'); + tile.classList.remove('showing'); + } + }); + }); + }); + + // FAB Menu + const fabBtn = document.getElementById('fabBtn'); + const fabMenu = document.getElementById('fabMenu'); + + fabBtn.addEventListener('click', () => { + fabMenu.classList.toggle('active'); + }); + + // FAB Options + const fabOptions = document.querySelectorAll('.fab-option'); + + fabOptions.forEach(option => { + option.addEventListener('click', () => { + const action = option.dataset.action; + + switch(action) { + case 'chat': + alert('Opening chat...'); + break; + case 'call': + window.location.href = 'tel:4155550123'; + break; + case 'book': + window.location.href = 'charter.html'; + break; + } + + fabMenu.classList.remove('active'); + }); + }); + + // Quick View for Yachts + const quickViewBtns = document.querySelectorAll('.quick-view-btn'); + + quickViewBtns.forEach(btn => { + btn.addEventListener('click', (e) => { + e.stopPropagation(); + const yachtTile = btn.closest('.tile-yacht'); + const yachtName = yachtTile.querySelector('.yacht-name').textContent; + + // Create modal + const modal = document.createElement('div'); + modal.className = 'yacht-modal'; + modal.innerHTML = ` + + `; + + document.body.appendChild(modal); + + // Add styles + const modalStyles = ` + .yacht-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.8); + display: flex; + align-items: center; + justify-content: center; + z-index: 300; + animation: fadeIn 0.3s ease; + } + + .modal-content { + background: white; + border-radius: 20px; + padding: 2rem; + max-width: 600px; + width: 90%; + max-height: 90vh; + overflow-y: auto; + position: relative; + animation: slideUp 0.3s ease; + } + + .modal-close { + position: absolute; + top: 1rem; + right: 1rem; + background: transparent; + border: none; + font-size: 2rem; + cursor: pointer; + } + + .modal-gallery img { + width: 100%; + border-radius: 10px; + margin: 1rem 0; + } + + .modal-details h3 { + margin: 1.5rem 0 1rem; + } + + .modal-details ul { + list-style: none; + padding: 0; + } + + .modal-details li { + padding: 0.5rem 0; + border-bottom: 1px solid #e2e8f0; + } + + .modal-actions { + display: flex; + gap: 1rem; + margin-top: 2rem; + } + + .modal-actions button { + flex: 1; + padding: 0.75rem; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + } + + @keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } + } + + @keyframes slideUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + `; + + if (!document.getElementById('modal-styles')) { + const styleSheet = document.createElement('style'); + styleSheet.id = 'modal-styles'; + styleSheet.textContent = modalStyles; + document.head.appendChild(styleSheet); + } + + // Close modal + modal.querySelector('.modal-close').addEventListener('click', () => { + modal.remove(); + }); + + modal.addEventListener('click', (e) => { + if (e.target === modal) { + modal.remove(); + } + }); + }); + }); + + // Wishlist functionality + let wishlist = []; + const wishlistSidebar = document.getElementById('wishlistSidebar'); + const wishlistContent = document.getElementById('wishlistContent'); + + // Add to wishlist buttons (would be added to tiles) + bentoTiles.forEach(tile => { + if (tile.classList.contains('tile-yacht') || tile.classList.contains('tile-experience')) { + const wishlistBtn = document.createElement('button'); + wishlistBtn.className = 'wishlist-add'; + wishlistBtn.innerHTML = 'โ™ก'; + wishlistBtn.style.cssText = ` + position: absolute; + top: 1rem; + left: 1rem; + background: white; + border: none; + width: 40px; + height: 40px; + border-radius: 50%; + font-size: 1.5rem; + cursor: pointer; + z-index: 10; + transition: all 0.3s ease; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + `; + + tile.appendChild(wishlistBtn); + + wishlistBtn.addEventListener('click', (e) => { + e.stopPropagation(); + const itemName = tile.querySelector('h3').textContent; + + if (wishlistBtn.innerHTML === 'โ™ก') { + wishlistBtn.innerHTML = 'โค๏ธ'; + wishlist.push(itemName); + updateWishlist(); + } else { + wishlistBtn.innerHTML = 'โ™ก'; + wishlist = wishlist.filter(item => item !== itemName); + updateWishlist(); + } + }); + } + }); + + function updateWishlist() { + if (wishlist.length === 0) { + wishlistContent.innerHTML = '

Select yachts and experiences to build your dream charter

'; + } else { + wishlistContent.innerHTML = ` +

Selected Items (${wishlist.length})

+
    + ${wishlist.map(item => ` +
  • + ${item} +
  • + `).join('')} +
+ `; + } + } + + // Show wishlist + document.addEventListener('keydown', (e) => { + if (e.key === 'w' && e.ctrlKey) { + e.preventDefault(); + wishlistSidebar.classList.toggle('active'); + } + }); + + document.getElementById('wishlistClose').addEventListener('click', () => { + wishlistSidebar.classList.remove('active'); + }); + + // Parallax effect on scroll + let ticking = false; + + function updateParallax() { + const scrolled = window.pageYOffset; + const heroTile = document.querySelector('.tile-hero'); + + if (heroTile) { + const heroVisual = heroTile.querySelector('.hero-visual'); + if (heroVisual) { + heroVisual.style.transform = `translateY(${scrolled * 0.3}px)`; + } + } + + ticking = false; + } + + function requestTick() { + if (!ticking) { + window.requestAnimationFrame(updateParallax); + ticking = true; + } + } + + window.addEventListener('scroll', requestTick); + + // Auto-play/pause videos on visibility + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + const video = entry.target.querySelector('video'); + if (video) { + if (entry.isIntersecting) { + video.play(); + } else { + video.pause(); + } + } + }); + }, { threshold: 0.5 }); + + document.querySelectorAll('.tile-video, .tile-hero').forEach(tile => { + observer.observe(tile); + }); + + // Tile click navigation + bentoTiles.forEach(tile => { + if (!tile.classList.contains('tile-yacht')) { + tile.addEventListener('click', () => { + if (tile.classList.contains('tile-service')) { + window.location.href = 'maintenance.html'; + } else if (tile.classList.contains('tile-experience')) { + window.location.href = 'charter.html'; + } else if (tile.classList.contains('tile-cta')) { + window.location.href = 'charter.html'; + } + }); + } + }); + + // Smooth hover effects + bentoTiles.forEach(tile => { + tile.addEventListener('mouseenter', () => { + tile.style.zIndex = '10'; + }); + + tile.addEventListener('mouseleave', () => { + setTimeout(() => { + tile.style.zIndex = ''; + }, 300); + }); + }); +}); \ No newline at end of file diff --git a/website-mockups/js-backup-20250918-135225/booking.js b/website-mockups/js-backup-20250918-135225/booking.js new file mode 100644 index 0000000..c6c3f26 --- /dev/null +++ b/website-mockups/js-backup-20250918-135225/booking.js @@ -0,0 +1,89 @@ +// HarborSmith - Booking JavaScript +// ================================ + +// Calendar functionality +document.addEventListener('DOMContentLoaded', () => { + // Calendar day selection + const calendarDays = document.querySelectorAll('.calendar-day.available'); + calendarDays.forEach(day => { + day.addEventListener('click', function() { + // Remove previous selection + document.querySelector('.calendar-day.selected')?.classList.remove('selected'); + // Add selection to clicked day + this.classList.add('selected'); + // Update summary + updateBookingSummary(); + }); + }); + + // Duration selection + const durationOptions = document.querySelectorAll('input[name="duration"]'); + durationOptions.forEach(option => { + option.addEventListener('change', updateBookingSummary); + }); + + // Guest number input + const guestInput = document.querySelector('input[type="number"]'); + if (guestInput) { + guestInput.addEventListener('change', updateBookingSummary); + } + + // Yacht selection (for booking page 2) + const yachtCards = document.querySelectorAll('.yacht-select-card'); + yachtCards.forEach(card => { + card.addEventListener('click', function() { + // Remove previous selection + document.querySelector('.yacht-select-card.selected')?.classList.remove('selected'); + // Add selection + this.classList.add('selected'); + updateBookingSummary(); + }); + }); + + // Addon selection (for booking page 3) + const addonCards = document.querySelectorAll('.addon-card'); + addonCards.forEach(card => { + card.addEventListener('click', function() { + this.classList.toggle('selected'); + updateBookingSummary(); + }); + }); +}); + +// Update booking summary +function updateBookingSummary() { + // This would normally update the summary sidebar with selected options + console.log('Updating booking summary...'); +} + +// Form validation for final step +function validateBookingForm() { + const form = document.getElementById('bookingForm'); + if (!form) return true; + + const inputs = form.querySelectorAll('[required]'); + let isValid = true; + + inputs.forEach(input => { + if (!input.value.trim()) { + input.classList.add('error'); + isValid = false; + } else { + input.classList.remove('error'); + } + }); + + return isValid; +} + +// Handle form submission +const bookingForm = document.getElementById('bookingForm'); +if (bookingForm) { + bookingForm.addEventListener('submit', (e) => { + e.preventDefault(); + if (validateBookingForm()) { + alert('Thank you for your booking request! We will contact you shortly to confirm your charter.'); + // In production, this would submit to a server + } + }); +} \ No newline at end of file diff --git a/website-mockups/js-backup-20250918-135225/main.js b/website-mockups/js-backup-20250918-135225/main.js new file mode 100644 index 0000000..90e2454 --- /dev/null +++ b/website-mockups/js-backup-20250918-135225/main.js @@ -0,0 +1,339 @@ +// HarborSmith - Main JavaScript +// ======================== + +// Theme Management +const themeManager = { + init() { + this.themeToggle = document.getElementById('themeToggle'); + this.themeDropdown = document.getElementById('themeDropdown'); + this.themeOptions = document.querySelectorAll('.theme-option'); + + // Load saved theme or default to nautical + const savedTheme = localStorage.getItem('harborsmith-theme') || 'nautical'; + this.setTheme(savedTheme); + + // Event listeners + this.themeToggle?.addEventListener('click', (e) => { + e.stopPropagation(); + this.themeDropdown.classList.toggle('active'); + }); + + this.themeOptions.forEach(option => { + option.addEventListener('click', (e) => { + const theme = e.currentTarget.dataset.theme; + this.setTheme(theme); + this.themeDropdown.classList.remove('active'); + }); + }); + + // Close dropdown when clicking outside + document.addEventListener('click', () => { + this.themeDropdown?.classList.remove('active'); + }); + }, + + setTheme(theme) { + document.documentElement.setAttribute('data-theme', theme); + localStorage.setItem('harborsmith-theme', theme); + + // Update active state + this.themeOptions.forEach(option => { + option.classList.toggle('active', option.dataset.theme === theme); + }); + } +}; + +// Navigation +const navigation = { + init() { + this.navbar = document.getElementById('navbar'); + this.navToggle = document.getElementById('navToggle'); + this.navMenu = document.getElementById('navMenu'); + this.navLinks = document.querySelectorAll('.nav-link'); + + // Mobile menu toggle + this.navToggle?.addEventListener('click', () => { + this.navToggle.classList.toggle('active'); + this.navMenu.classList.toggle('active'); + }); + + // Close mobile menu on link click + this.navLinks.forEach(link => { + link.addEventListener('click', () => { + this.navToggle?.classList.remove('active'); + this.navMenu?.classList.remove('active'); + }); + }); + + // Scroll behavior + window.addEventListener('scroll', () => { + if (window.scrollY > 50) { + this.navbar?.classList.add('scrolled'); + } else { + this.navbar?.classList.remove('scrolled'); + } + }); + + // Active link highlighting + this.updateActiveLink(); + }, + + updateActiveLink() { + const currentPath = window.location.pathname.split('/').pop() || 'index.html'; + this.navLinks.forEach(link => { + const href = link.getAttribute('href'); + if (href === currentPath) { + link.classList.add('active'); + } + }); + } +}; + +// Smooth Scrolling +const smoothScroll = { + init() { + document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', (e) => { + e.preventDefault(); + const target = document.querySelector(anchor.getAttribute('href')); + if (target) { + target.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } + }); + }); + } +}; + +// Testimonial Slider +const testimonialSlider = { + init() { + this.cards = document.querySelectorAll('.testimonial-card'); + this.dots = document.querySelectorAll('.dot'); + this.currentIndex = 0; + + if (this.cards.length === 0) return; + + // Auto-play + this.startAutoPlay(); + + // Dot navigation + this.dots.forEach((dot, index) => { + dot.addEventListener('click', () => { + this.goToSlide(index); + this.resetAutoPlay(); + }); + }); + }, + + goToSlide(index) { + this.cards[this.currentIndex]?.classList.remove('active'); + this.dots[this.currentIndex]?.classList.remove('active'); + + this.currentIndex = index; + + this.cards[this.currentIndex]?.classList.add('active'); + this.dots[this.currentIndex]?.classList.add('active'); + }, + + nextSlide() { + const nextIndex = (this.currentIndex + 1) % this.cards.length; + this.goToSlide(nextIndex); + }, + + startAutoPlay() { + this.autoPlayInterval = setInterval(() => { + this.nextSlide(); + }, 5000); + }, + + resetAutoPlay() { + clearInterval(this.autoPlayInterval); + this.startAutoPlay(); + } +}; + +// Counter Animation +const counterAnimation = { + init() { + this.counters = document.querySelectorAll('.stat-number'); + this.animated = false; + + if (this.counters.length === 0) return; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting && !this.animated) { + this.animateCounters(); + this.animated = true; + } + }); + }, { threshold: 0.5 }); + + const statsSection = document.querySelector('.stats-section'); + if (statsSection) { + observer.observe(statsSection); + } + }, + + animateCounters() { + this.counters.forEach(counter => { + const target = parseInt(counter.dataset.count); + const duration = 2000; + const increment = target / (duration / 16); + let current = 0; + + const updateCounter = () => { + current += increment; + if (current < target) { + counter.textContent = Math.floor(current); + requestAnimationFrame(updateCounter); + } else { + counter.textContent = target; + } + }; + + updateCounter(); + }); + } +}; + +// Form Validation +const formValidation = { + init() { + const forms = document.querySelectorAll('form'); + forms.forEach(form => { + form.addEventListener('submit', (e) => { + if (!this.validateForm(form)) { + e.preventDefault(); + } + }); + + // Real-time validation + const inputs = form.querySelectorAll('input, textarea, select'); + inputs.forEach(input => { + input.addEventListener('blur', () => { + this.validateField(input); + }); + }); + }); + }, + + validateForm(form) { + const inputs = form.querySelectorAll('[required]'); + let isValid = true; + + inputs.forEach(input => { + if (!this.validateField(input)) { + isValid = false; + } + }); + + return isValid; + }, + + validateField(field) { + const value = field.value.trim(); + const type = field.type; + let isValid = true; + + // Remove previous error + field.classList.remove('error'); + const errorMsg = field.parentElement.querySelector('.error-message'); + if (errorMsg) { + errorMsg.remove(); + } + + // Required field + if (field.hasAttribute('required') && !value) { + this.showError(field, 'This field is required'); + return false; + } + + // Email validation + if (type === 'email' && value) { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(value)) { + this.showError(field, 'Please enter a valid email address'); + return false; + } + } + + // Phone validation + if (type === 'tel' && value) { + const phoneRegex = /^[\d\s\-\+\(\)]+$/; + if (!phoneRegex.test(value)) { + this.showError(field, 'Please enter a valid phone number'); + return false; + } + } + + return isValid; + }, + + showError(field, message) { + field.classList.add('error'); + const errorElement = document.createElement('div'); + errorElement.className = 'error-message'; + errorElement.textContent = message; + field.parentElement.appendChild(errorElement); + } +}; + +// Yacht Quick View Modal +const yachtModal = { + init() { + const viewButtons = document.querySelectorAll('.yacht-view-btn'); + viewButtons.forEach(btn => { + btn.addEventListener('click', (e) => { + e.preventDefault(); + this.openModal(); + }); + }); + }, + + openModal() { + // For now, just alert - in production, would open a modal + alert('Quick view modal would open here with yacht details and 360ยฐ view'); + } +}; + +// Parallax Effects +const parallaxEffects = { + init() { + this.elements = document.querySelectorAll('.parallax'); + + if (this.elements.length === 0) return; + + window.addEventListener('scroll', () => { + this.updateParallax(); + }); + }, + + updateParallax() { + const scrolled = window.pageYOffset; + + this.elements.forEach(element => { + const rate = element.dataset.rate || 0.5; + const yPos = -(scrolled * rate); + element.style.transform = `translateY(${yPos}px)`; + }); + } +}; + +// Initialize everything when DOM is ready +document.addEventListener('DOMContentLoaded', () => { + themeManager.init(); + navigation.init(); + smoothScroll.init(); + testimonialSlider.init(); + counterAnimation.init(); + formValidation.init(); + yachtModal.init(); + parallaxEffects.init(); + + // Log successful initialization + console.log('HarborSmith website initialized successfully!'); +}); \ No newline at end of file diff --git a/website-mockups/js-backup-20250918-135225/voyage-layout.js b/website-mockups/js-backup-20250918-135225/voyage-layout.js new file mode 100644 index 0000000..4bb6739 --- /dev/null +++ b/website-mockups/js-backup-20250918-135225/voyage-layout.js @@ -0,0 +1,355 @@ +// Voyage Layout JavaScript - Smooth, Interactive, Engaging + +document.addEventListener('DOMContentLoaded', function() { + // Theme Switcher + const themeToggle = document.getElementById('themeToggle'); + const themeDropdown = document.getElementById('themeDropdown'); + const themeOptions = document.querySelectorAll('.theme-option'); + + if (themeToggle && themeDropdown) { + themeToggle.addEventListener('click', (e) => { + e.stopPropagation(); + themeDropdown.classList.toggle('active'); + }); + + themeOptions.forEach(option => { + option.addEventListener('click', () => { + const theme = option.dataset.theme; + document.body.className = theme === 'nautical' ? '' : `theme-${theme}`; + localStorage.setItem('selectedTheme', theme); + themeDropdown.classList.remove('active'); + }); + }); + + // Close dropdown when clicking outside + document.addEventListener('click', () => { + themeDropdown.classList.remove('active'); + }); + + // Load saved theme + const savedTheme = localStorage.getItem('selectedTheme'); + if (savedTheme && savedTheme !== 'nautical') { + document.body.className = `theme-${savedTheme}`; + } + } + + // Navigation scroll effect + const nav = document.getElementById('voyageNav'); + let lastScroll = 0; + + window.addEventListener('scroll', () => { + const currentScroll = window.pageYOffset; + + if (currentScroll > 50) { + nav.classList.add('scrolled'); + } else { + nav.classList.remove('scrolled'); + } + + lastScroll = currentScroll; + }); + + // Smooth scroll for navigation links + document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', function(e) { + e.preventDefault(); + const target = document.querySelector(this.getAttribute('href')); + if (target) { + target.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } + }); + }); + + // Parallax effect for hero video + const heroSection = document.getElementById('heroSection'); + const heroVideo = document.querySelector('.hero-video-container'); + + window.addEventListener('scroll', () => { + const scrolled = window.pageYOffset; + const rate = scrolled * 0.5; + + if (heroVideo) { + heroVideo.style.transform = `translateY(${rate}px)`; + } + }); + + // Fleet Carousel + const yachtCards = document.querySelectorAll('.yacht-card'); + const dots = document.querySelectorAll('.dot'); + const prevBtn = document.querySelector('.fleet-prev'); + const nextBtn = document.querySelector('.fleet-next'); + let currentYacht = 0; + + function showYacht(index) { + yachtCards.forEach(card => card.classList.remove('active')); + dots.forEach(dot => dot.classList.remove('active')); + + yachtCards[index].classList.add('active'); + dots[index].classList.add('active'); + } + + if (prevBtn && nextBtn) { + prevBtn.addEventListener('click', () => { + currentYacht = (currentYacht - 1 + yachtCards.length) % yachtCards.length; + showYacht(currentYacht); + }); + + nextBtn.addEventListener('click', () => { + currentYacht = (currentYacht + 1) % yachtCards.length; + showYacht(currentYacht); + }); + } + + dots.forEach((dot, index) => { + dot.addEventListener('click', () => { + currentYacht = index; + showYacht(currentYacht); + }); + }); + + // Auto-rotate fleet carousel + setInterval(() => { + if (document.visibilityState === 'visible') { + currentYacht = (currentYacht + 1) % yachtCards.length; + showYacht(currentYacht); + } + }, 5000); + + // CTA Button interactions + document.querySelectorAll('.btn-primary-warm, .btn-secondary-warm').forEach(btn => { + btn.addEventListener('click', function(e) { + // Create ripple effect + const ripple = document.createElement('span'); + ripple.classList.add('ripple'); + this.appendChild(ripple); + + const rect = this.getBoundingClientRect(); + const size = Math.max(rect.width, rect.height); + const x = e.clientX - rect.left - size / 2; + const y = e.clientY - rect.top - size / 2; + + ripple.style.width = ripple.style.height = size + 'px'; + ripple.style.left = x + 'px'; + ripple.style.top = y + 'px'; + + setTimeout(() => ripple.remove(), 600); + }); + }); + + // Add ripple styles + const style = document.createElement('style'); + style.textContent = ` + .ripple { + position: absolute; + border-radius: 50%; + background: rgba(255, 255, 255, 0.6); + transform: scale(0); + animation: ripple 0.6s ease-out; + pointer-events: none; + } + + @keyframes ripple { + to { + transform: scale(4); + opacity: 0; + } + } + + .btn-primary-warm, + .btn-secondary-warm { + position: relative; + overflow: hidden; + } + `; + document.head.appendChild(style); + + // Animate elements on scroll + const observerOptions = { + threshold: 0.1, + rootMargin: '0px 0px -100px 0px' + }; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.style.opacity = '1'; + entry.target.style.transform = 'translateY(0)'; + } + }); + }, observerOptions); + + // Observe elements for animation + document.querySelectorAll('.welcome-content, .story-card, .booking-card').forEach(el => { + el.style.opacity = '0'; + el.style.transform = 'translateY(30px)'; + el.style.transition = 'all 0.6s ease'; + observer.observe(el); + }); + + // Interactive booking cards + document.querySelectorAll('.booking-card').forEach(card => { + card.addEventListener('mouseenter', function() { + this.style.transform = 'translateY(-10px) scale(1.02)'; + }); + + card.addEventListener('mouseleave', function() { + if (this.classList.contains('featured')) { + this.style.transform = 'scale(1.05)'; + } else { + this.style.transform = 'translateY(0) scale(1)'; + } + }); + }); + + // Phone number click handler + document.querySelectorAll('.btn-booking.primary').forEach(btn => { + if (btn.textContent.includes('415')) { + btn.addEventListener('click', () => { + window.location.href = 'tel:4155550123'; + }); + } + }); + + // Video optimization - pause when not visible + const video = document.querySelector('.hero-video'); + if (video) { + const videoObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + video.play(); + } else { + video.pause(); + } + }); + }, { threshold: 0.25 }); + + videoObserver.observe(video); + } + + // Story cards hover effect + document.querySelectorAll('.story-card').forEach(card => { + const img = card.querySelector('img'); + + card.addEventListener('mouseenter', () => { + if (img) { + img.style.transform = 'scale(1.1)'; + img.style.transition = 'transform 0.6s ease'; + } + }); + + card.addEventListener('mouseleave', () => { + if (img) { + img.style.transform = 'scale(1)'; + } + }); + }); + + // Dynamic trust badge counter - starts from 0 + // Target the white text span specifically (the second span in trust-badge) + const trustBadge = document.querySelector('.trust-badge > span:last-child'); + if (trustBadge) { + let count = 0; + const targetCount = 500; + const duration = 2000; // 2 seconds + const steps = 50; + const increment = targetCount / steps; + const stepDuration = duration / steps; + + // Start with 0 + trustBadge.textContent = `Trusted by 0+ adventurers`; + + const counter = setInterval(() => { + count += increment; + if (count >= targetCount) { + count = targetCount; + clearInterval(counter); + trustBadge.textContent = `Trusted by 500+ adventurers`; + } else { + trustBadge.textContent = `Trusted by ${Math.floor(count)}+ adventurers`; + } + }, stepDuration); + } + + // Remove any duplicate red text that might exist + const allSpans = document.querySelectorAll('.hero-content span'); + allSpans.forEach(span => { + // Check if this is the red duplicate text (not inside trust-badge) + if (span.textContent.includes('Trusted by') && + span.textContent.includes('adventurers') && + !span.closest('.trust-badge')) { + span.style.display = 'none'; + } + }); + + // Smooth hover for navigation links + document.querySelectorAll('.nav-link').forEach(link => { + link.addEventListener('mouseenter', function() { + this.style.transform = 'translateY(-2px)'; + }); + + link.addEventListener('mouseleave', function() { + this.style.transform = 'translateY(0)'; + }); + }); + + // Mobile menu (would need to add hamburger menu for production) + const mobileMenuBtn = document.createElement('button'); + mobileMenuBtn.className = 'mobile-menu-btn'; + mobileMenuBtn.innerHTML = ''; + mobileMenuBtn.style.cssText = ` + display: none; + flex-direction: column; + gap: 4px; + background: transparent; + border: none; + cursor: pointer; + padding: 0.5rem; + `; + + // Add mobile menu styles + const mobileStyles = document.createElement('style'); + mobileStyles.textContent = ` + @media (max-width: 768px) { + .mobile-menu-btn { + display: flex !important; + } + + .mobile-menu-btn span { + width: 24px; + height: 2px; + background: white; + transition: all 0.3s ease; + } + + .voyage-nav.scrolled .mobile-menu-btn span { + background: var(--primary-blue); + } + } + `; + document.head.appendChild(mobileStyles); + + // Add mobile menu button to nav + const navContainer = document.querySelector('.nav-container'); + if (navContainer && window.innerWidth <= 768) { + navContainer.appendChild(mobileMenuBtn); + } + + // Loading animation for images + document.querySelectorAll('img').forEach(img => { + // Check if image is already loaded + if (img.complete && img.naturalHeight !== 0) { + img.style.opacity = '1'; + } else { + img.style.opacity = '0'; + img.addEventListener('load', function() { + this.style.opacity = '1'; + }); + } + img.style.transition = 'opacity 0.6s ease'; + }); + + console.log('Voyage layout initialized successfully!'); +}); \ No newline at end of file diff --git a/website-mockups/js/animations.js b/website-mockups/js/animations.js new file mode 100644 index 0000000..27f355e --- /dev/null +++ b/website-mockups/js/animations.js @@ -0,0 +1,349 @@ +// HarborSmith - Animation Scripts +// ================================ + +// Scroll Animation Observer +const scrollAnimations = { + init() { + this.observeElements(); + this.initProgressBars(); + this.initTextAnimations(); + }, + + observeElements() { + const options = { + threshold: 0.1, + rootMargin: '0px 0px -100px 0px' + }; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('visible'); + + // Trigger specific animations + if (entry.target.classList.contains('counter-animate')) { + this.animateValue(entry.target); + } + + // Only animate once + if (!entry.target.classList.contains('repeat-animation')) { + observer.unobserve(entry.target); + } + } + }); + }, options); + + // Observe all animated elements + const animatedElements = document.querySelectorAll('.scroll-animate, .scroll-animate-left, .scroll-animate-right, .scroll-animate-scale, .counter-animate'); + animatedElements.forEach(el => observer.observe(el)); + }, + + animateValue(element) { + const target = parseInt(element.dataset.target); + const duration = parseInt(element.dataset.duration) || 2000; + const start = 0; + const increment = target / (duration / 16); + let current = start; + + const updateValue = () => { + current += increment; + if (current < target) { + element.textContent = Math.floor(current); + requestAnimationFrame(updateValue); + } else { + element.textContent = target; + } + }; + + updateValue(); + }, + + initProgressBars() { + const progressBars = document.querySelectorAll('.progress-bar-fill'); + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const progressBar = entry.target; + const targetWidth = progressBar.dataset.progress || '100'; + progressBar.style.setProperty('--progress', targetWidth + '%'); + progressBar.classList.add('animate'); + observer.unobserve(progressBar); + } + }); + }, { threshold: 0.5 }); + + progressBars.forEach(bar => observer.observe(bar)); + }, + + initTextAnimations() { + const textElements = document.querySelectorAll('.text-animate'); + + textElements.forEach(element => { + const text = element.textContent; + element.textContent = ''; + + [...text].forEach((char, index) => { + const span = document.createElement('span'); + span.textContent = char === ' ' ? '\u00A0' : char; + span.style.animationDelay = `${index * 0.05}s`; + span.classList.add('char-animate'); + element.appendChild(span); + }); + }); + } +}; + +// Cursor Effects +const cursorEffects = { + init() { + this.cursor = document.createElement('div'); + this.cursor.className = 'custom-cursor'; + document.body.appendChild(this.cursor); + + this.follower = document.createElement('div'); + this.follower.className = 'cursor-follower'; + document.body.appendChild(this.follower); + + this.initEventListeners(); + }, + + initEventListeners() { + document.addEventListener('mousemove', (e) => { + this.cursor.style.left = e.clientX + 'px'; + this.cursor.style.top = e.clientY + 'px'; + + setTimeout(() => { + this.follower.style.left = e.clientX + 'px'; + this.follower.style.top = e.clientY + 'px'; + }, 100); + }); + + // Add hover effects + const interactiveElements = document.querySelectorAll('a, button, .clickable'); + interactiveElements.forEach(el => { + el.addEventListener('mouseenter', () => { + this.cursor.classList.add('hover'); + this.follower.classList.add('hover'); + }); + + el.addEventListener('mouseleave', () => { + this.cursor.classList.remove('hover'); + this.follower.classList.remove('hover'); + }); + }); + } +}; + +// Ripple Effect +const rippleEffect = { + init() { + const buttons = document.querySelectorAll('.btn, .ripple'); + + buttons.forEach(button => { + button.addEventListener('click', (e) => { + const ripple = document.createElement('span'); + ripple.className = 'ripple-effect'; + + const rect = button.getBoundingClientRect(); + const size = Math.max(rect.width, rect.height); + const x = e.clientX - rect.left - size / 2; + const y = e.clientY - rect.top - size / 2; + + ripple.style.width = ripple.style.height = size + 'px'; + ripple.style.left = x + 'px'; + ripple.style.top = y + 'px'; + + button.appendChild(ripple); + + setTimeout(() => { + ripple.remove(); + }, 600); + }); + }); + } +}; + +// Magnetic Buttons +const magneticButtons = { + init() { + const magneticElements = document.querySelectorAll('.magnetic'); + + magneticElements.forEach(element => { + element.addEventListener('mousemove', (e) => { + const rect = element.getBoundingClientRect(); + const x = e.clientX - rect.left - rect.width / 2; + const y = e.clientY - rect.top - rect.height / 2; + + element.style.transform = `translate(${x * 0.2}px, ${y * 0.2}px)`; + }); + + element.addEventListener('mouseleave', () => { + element.style.transform = 'translate(0, 0)'; + }); + }); + } +}; + +// Page Loader +const pageLoader = { + init() { + const loader = document.createElement('div'); + loader.className = 'page-loader'; + loader.innerHTML = ` +
+
+ + + + + +
+
Setting Sail...
+
+ `; + + document.body.appendChild(loader); + + window.addEventListener('load', () => { + setTimeout(() => { + loader.classList.add('fade-out'); + setTimeout(() => { + loader.remove(); + }, 500); + }, 1000); + }); + } +}; + +// Smooth Page Transitions +const pageTransitions = { + init() { + const links = document.querySelectorAll('a[href^="/"], a[href^="./"], a[href$=".html"]'); + + links.forEach(link => { + link.addEventListener('click', (e) => { + const href = link.getAttribute('href'); + + // Skip if it's an anchor link or external link + if (href.startsWith('#') || href.startsWith('http')) return; + + e.preventDefault(); + + document.body.classList.add('page-transition'); + + setTimeout(() => { + window.location.href = href; + }, 500); + }); + }); + } +}; + +// Initialize animations +document.addEventListener('DOMContentLoaded', () => { + scrollAnimations.init(); + rippleEffect.init(); + magneticButtons.init(); + // Uncomment these for production + // cursorEffects.init(); + // pageLoader.init(); + // pageTransitions.init(); +}); + +// Add some CSS for the loader (normally would be in a separate file) +const loaderStyles = ` + +`; + +// Inject loader styles +document.head.insertAdjacentHTML('beforeend', loaderStyles); \ No newline at end of file diff --git a/website-mockups/js/bento-layout.js b/website-mockups/js/bento-layout.js new file mode 100644 index 0000000..120caf7 --- /dev/null +++ b/website-mockups/js/bento-layout.js @@ -0,0 +1,375 @@ +// Bento Grid Layout JavaScript + +document.addEventListener('DOMContentLoaded', function() { + // Menu Toggle + const menuToggle = document.getElementById('menuToggle'); + const menuOverlay = document.getElementById('menuOverlay'); + const menuClose = document.getElementById('menuClose'); + + menuToggle.addEventListener('click', () => { + menuOverlay.classList.add('active'); + }); + + menuClose.addEventListener('click', () => { + menuOverlay.classList.remove('active'); + }); + + // Filter Toggle + const filterToggle = document.getElementById('filterToggle'); + const filterBar = document.getElementById('filterBar'); + + filterToggle.addEventListener('click', () => { + filterBar.classList.toggle('active'); + }); + + // Filter Functionality + const filterChips = document.querySelectorAll('.filter-chip'); + const bentoTiles = document.querySelectorAll('.bento-tile'); + + filterChips.forEach(chip => { + chip.addEventListener('click', () => { + // Update active state + filterChips.forEach(c => c.classList.remove('active')); + chip.classList.add('active'); + + const filter = chip.dataset.filter; + + // Filter tiles + bentoTiles.forEach(tile => { + if (filter === 'all' || tile.dataset.category === filter) { + tile.classList.remove('hiding'); + tile.classList.add('showing'); + } else { + tile.classList.add('hiding'); + tile.classList.remove('showing'); + } + }); + }); + }); + + // FAB Menu + const fabBtn = document.getElementById('fabBtn'); + const fabMenu = document.getElementById('fabMenu'); + + fabBtn.addEventListener('click', () => { + fabMenu.classList.toggle('active'); + }); + + // FAB Options + const fabOptions = document.querySelectorAll('.fab-option'); + + fabOptions.forEach(option => { + option.addEventListener('click', () => { + const action = option.dataset.action; + + switch(action) { + case 'chat': + alert('Opening chat...'); + break; + case 'call': + window.location.href = 'tel:4155550123'; + break; + case 'book': + window.location.href = 'charter.html'; + break; + } + + fabMenu.classList.remove('active'); + }); + }); + + // Quick View for Yachts + const quickViewBtns = document.querySelectorAll('.quick-view-btn'); + + quickViewBtns.forEach(btn => { + btn.addEventListener('click', (e) => { + e.stopPropagation(); + const yachtTile = btn.closest('.tile-yacht'); + const yachtName = yachtTile.querySelector('.yacht-name').textContent; + + // Create modal + const modal = document.createElement('div'); + modal.className = 'yacht-modal'; + modal.innerHTML = ` + + `; + + document.body.appendChild(modal); + + // Add styles + const modalStyles = ` + .yacht-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.8); + display: flex; + align-items: center; + justify-content: center; + z-index: 300; + animation: fadeIn 0.3s ease; + } + + .modal-content { + background: white; + border-radius: 20px; + padding: 2rem; + max-width: 600px; + width: 90%; + max-height: 90vh; + overflow-y: auto; + position: relative; + animation: slideUp 0.3s ease; + } + + .modal-close { + position: absolute; + top: 1rem; + right: 1rem; + background: transparent; + border: none; + font-size: 2rem; + cursor: pointer; + } + + .modal-gallery img { + width: 100%; + border-radius: 10px; + margin: 1rem 0; + } + + .modal-details h3 { + margin: 1.5rem 0 1rem; + } + + .modal-details ul { + list-style: none; + padding: 0; + } + + .modal-details li { + padding: 0.5rem 0; + border-bottom: 1px solid #e2e8f0; + } + + .modal-actions { + display: flex; + gap: 1rem; + margin-top: 2rem; + } + + .modal-actions button { + flex: 1; + padding: 0.75rem; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + } + + @keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } + } + + @keyframes slideUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + `; + + if (!document.getElementById('modal-styles')) { + const styleSheet = document.createElement('style'); + styleSheet.id = 'modal-styles'; + styleSheet.textContent = modalStyles; + document.head.appendChild(styleSheet); + } + + // Close modal + modal.querySelector('.modal-close').addEventListener('click', () => { + modal.remove(); + }); + + modal.addEventListener('click', (e) => { + if (e.target === modal) { + modal.remove(); + } + }); + }); + }); + + // Wishlist functionality + let wishlist = []; + const wishlistSidebar = document.getElementById('wishlistSidebar'); + const wishlistContent = document.getElementById('wishlistContent'); + + // Add to wishlist buttons (would be added to tiles) + bentoTiles.forEach(tile => { + if (tile.classList.contains('tile-yacht') || tile.classList.contains('tile-experience')) { + const wishlistBtn = document.createElement('button'); + wishlistBtn.className = 'wishlist-add'; + wishlistBtn.innerHTML = 'โ™ก'; + wishlistBtn.style.cssText = ` + position: absolute; + top: 1rem; + left: 1rem; + background: white; + border: none; + width: 40px; + height: 40px; + border-radius: 50%; + font-size: 1.5rem; + cursor: pointer; + z-index: 10; + transition: all 0.3s ease; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + `; + + tile.appendChild(wishlistBtn); + + wishlistBtn.addEventListener('click', (e) => { + e.stopPropagation(); + const itemName = tile.querySelector('h3').textContent; + + if (wishlistBtn.innerHTML === 'โ™ก') { + wishlistBtn.innerHTML = 'โค๏ธ'; + wishlist.push(itemName); + updateWishlist(); + } else { + wishlistBtn.innerHTML = 'โ™ก'; + wishlist = wishlist.filter(item => item !== itemName); + updateWishlist(); + } + }); + } + }); + + function updateWishlist() { + if (wishlist.length === 0) { + wishlistContent.innerHTML = '

Select yachts and experiences to build your dream charter

'; + } else { + wishlistContent.innerHTML = ` +

Selected Items (${wishlist.length})

+
    + ${wishlist.map(item => ` +
  • + ${item} +
  • + `).join('')} +
+ `; + } + } + + // Show wishlist + document.addEventListener('keydown', (e) => { + if (e.key === 'w' && e.ctrlKey) { + e.preventDefault(); + wishlistSidebar.classList.toggle('active'); + } + }); + + document.getElementById('wishlistClose').addEventListener('click', () => { + wishlistSidebar.classList.remove('active'); + }); + + // Parallax effect on scroll + let ticking = false; + + function updateParallax() { + const scrolled = window.pageYOffset; + const heroTile = document.querySelector('.tile-hero'); + + if (heroTile) { + const heroVisual = heroTile.querySelector('.hero-visual'); + if (heroVisual) { + heroVisual.style.transform = `translateY(${scrolled * 0.3}px)`; + } + } + + ticking = false; + } + + function requestTick() { + if (!ticking) { + window.requestAnimationFrame(updateParallax); + ticking = true; + } + } + + window.addEventListener('scroll', requestTick); + + // Auto-play/pause videos on visibility + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + const video = entry.target.querySelector('video'); + if (video) { + if (entry.isIntersecting) { + video.play(); + } else { + video.pause(); + } + } + }); + }, { threshold: 0.5 }); + + document.querySelectorAll('.tile-video, .tile-hero').forEach(tile => { + observer.observe(tile); + }); + + // Tile click navigation + bentoTiles.forEach(tile => { + if (!tile.classList.contains('tile-yacht')) { + tile.addEventListener('click', () => { + if (tile.classList.contains('tile-service')) { + window.location.href = 'maintenance.html'; + } else if (tile.classList.contains('tile-experience')) { + window.location.href = 'charter.html'; + } else if (tile.classList.contains('tile-cta')) { + window.location.href = 'charter.html'; + } + }); + } + }); + + // Smooth hover effects + bentoTiles.forEach(tile => { + tile.addEventListener('mouseenter', () => { + tile.style.zIndex = '10'; + }); + + tile.addEventListener('mouseleave', () => { + setTimeout(() => { + tile.style.zIndex = ''; + }, 300); + }); + }); +}); \ No newline at end of file diff --git a/website-mockups/js/booking.js b/website-mockups/js/booking.js new file mode 100644 index 0000000..c6c3f26 --- /dev/null +++ b/website-mockups/js/booking.js @@ -0,0 +1,89 @@ +// HarborSmith - Booking JavaScript +// ================================ + +// Calendar functionality +document.addEventListener('DOMContentLoaded', () => { + // Calendar day selection + const calendarDays = document.querySelectorAll('.calendar-day.available'); + calendarDays.forEach(day => { + day.addEventListener('click', function() { + // Remove previous selection + document.querySelector('.calendar-day.selected')?.classList.remove('selected'); + // Add selection to clicked day + this.classList.add('selected'); + // Update summary + updateBookingSummary(); + }); + }); + + // Duration selection + const durationOptions = document.querySelectorAll('input[name="duration"]'); + durationOptions.forEach(option => { + option.addEventListener('change', updateBookingSummary); + }); + + // Guest number input + const guestInput = document.querySelector('input[type="number"]'); + if (guestInput) { + guestInput.addEventListener('change', updateBookingSummary); + } + + // Yacht selection (for booking page 2) + const yachtCards = document.querySelectorAll('.yacht-select-card'); + yachtCards.forEach(card => { + card.addEventListener('click', function() { + // Remove previous selection + document.querySelector('.yacht-select-card.selected')?.classList.remove('selected'); + // Add selection + this.classList.add('selected'); + updateBookingSummary(); + }); + }); + + // Addon selection (for booking page 3) + const addonCards = document.querySelectorAll('.addon-card'); + addonCards.forEach(card => { + card.addEventListener('click', function() { + this.classList.toggle('selected'); + updateBookingSummary(); + }); + }); +}); + +// Update booking summary +function updateBookingSummary() { + // This would normally update the summary sidebar with selected options + console.log('Updating booking summary...'); +} + +// Form validation for final step +function validateBookingForm() { + const form = document.getElementById('bookingForm'); + if (!form) return true; + + const inputs = form.querySelectorAll('[required]'); + let isValid = true; + + inputs.forEach(input => { + if (!input.value.trim()) { + input.classList.add('error'); + isValid = false; + } else { + input.classList.remove('error'); + } + }); + + return isValid; +} + +// Handle form submission +const bookingForm = document.getElementById('bookingForm'); +if (bookingForm) { + bookingForm.addEventListener('submit', (e) => { + e.preventDefault(); + if (validateBookingForm()) { + alert('Thank you for your booking request! We will contact you shortly to confirm your charter.'); + // In production, this would submit to a server + } + }); +} \ No newline at end of file diff --git a/website-mockups/js/main.js b/website-mockups/js/main.js new file mode 100644 index 0000000..90e2454 --- /dev/null +++ b/website-mockups/js/main.js @@ -0,0 +1,339 @@ +// HarborSmith - Main JavaScript +// ======================== + +// Theme Management +const themeManager = { + init() { + this.themeToggle = document.getElementById('themeToggle'); + this.themeDropdown = document.getElementById('themeDropdown'); + this.themeOptions = document.querySelectorAll('.theme-option'); + + // Load saved theme or default to nautical + const savedTheme = localStorage.getItem('harborsmith-theme') || 'nautical'; + this.setTheme(savedTheme); + + // Event listeners + this.themeToggle?.addEventListener('click', (e) => { + e.stopPropagation(); + this.themeDropdown.classList.toggle('active'); + }); + + this.themeOptions.forEach(option => { + option.addEventListener('click', (e) => { + const theme = e.currentTarget.dataset.theme; + this.setTheme(theme); + this.themeDropdown.classList.remove('active'); + }); + }); + + // Close dropdown when clicking outside + document.addEventListener('click', () => { + this.themeDropdown?.classList.remove('active'); + }); + }, + + setTheme(theme) { + document.documentElement.setAttribute('data-theme', theme); + localStorage.setItem('harborsmith-theme', theme); + + // Update active state + this.themeOptions.forEach(option => { + option.classList.toggle('active', option.dataset.theme === theme); + }); + } +}; + +// Navigation +const navigation = { + init() { + this.navbar = document.getElementById('navbar'); + this.navToggle = document.getElementById('navToggle'); + this.navMenu = document.getElementById('navMenu'); + this.navLinks = document.querySelectorAll('.nav-link'); + + // Mobile menu toggle + this.navToggle?.addEventListener('click', () => { + this.navToggle.classList.toggle('active'); + this.navMenu.classList.toggle('active'); + }); + + // Close mobile menu on link click + this.navLinks.forEach(link => { + link.addEventListener('click', () => { + this.navToggle?.classList.remove('active'); + this.navMenu?.classList.remove('active'); + }); + }); + + // Scroll behavior + window.addEventListener('scroll', () => { + if (window.scrollY > 50) { + this.navbar?.classList.add('scrolled'); + } else { + this.navbar?.classList.remove('scrolled'); + } + }); + + // Active link highlighting + this.updateActiveLink(); + }, + + updateActiveLink() { + const currentPath = window.location.pathname.split('/').pop() || 'index.html'; + this.navLinks.forEach(link => { + const href = link.getAttribute('href'); + if (href === currentPath) { + link.classList.add('active'); + } + }); + } +}; + +// Smooth Scrolling +const smoothScroll = { + init() { + document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', (e) => { + e.preventDefault(); + const target = document.querySelector(anchor.getAttribute('href')); + if (target) { + target.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } + }); + }); + } +}; + +// Testimonial Slider +const testimonialSlider = { + init() { + this.cards = document.querySelectorAll('.testimonial-card'); + this.dots = document.querySelectorAll('.dot'); + this.currentIndex = 0; + + if (this.cards.length === 0) return; + + // Auto-play + this.startAutoPlay(); + + // Dot navigation + this.dots.forEach((dot, index) => { + dot.addEventListener('click', () => { + this.goToSlide(index); + this.resetAutoPlay(); + }); + }); + }, + + goToSlide(index) { + this.cards[this.currentIndex]?.classList.remove('active'); + this.dots[this.currentIndex]?.classList.remove('active'); + + this.currentIndex = index; + + this.cards[this.currentIndex]?.classList.add('active'); + this.dots[this.currentIndex]?.classList.add('active'); + }, + + nextSlide() { + const nextIndex = (this.currentIndex + 1) % this.cards.length; + this.goToSlide(nextIndex); + }, + + startAutoPlay() { + this.autoPlayInterval = setInterval(() => { + this.nextSlide(); + }, 5000); + }, + + resetAutoPlay() { + clearInterval(this.autoPlayInterval); + this.startAutoPlay(); + } +}; + +// Counter Animation +const counterAnimation = { + init() { + this.counters = document.querySelectorAll('.stat-number'); + this.animated = false; + + if (this.counters.length === 0) return; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting && !this.animated) { + this.animateCounters(); + this.animated = true; + } + }); + }, { threshold: 0.5 }); + + const statsSection = document.querySelector('.stats-section'); + if (statsSection) { + observer.observe(statsSection); + } + }, + + animateCounters() { + this.counters.forEach(counter => { + const target = parseInt(counter.dataset.count); + const duration = 2000; + const increment = target / (duration / 16); + let current = 0; + + const updateCounter = () => { + current += increment; + if (current < target) { + counter.textContent = Math.floor(current); + requestAnimationFrame(updateCounter); + } else { + counter.textContent = target; + } + }; + + updateCounter(); + }); + } +}; + +// Form Validation +const formValidation = { + init() { + const forms = document.querySelectorAll('form'); + forms.forEach(form => { + form.addEventListener('submit', (e) => { + if (!this.validateForm(form)) { + e.preventDefault(); + } + }); + + // Real-time validation + const inputs = form.querySelectorAll('input, textarea, select'); + inputs.forEach(input => { + input.addEventListener('blur', () => { + this.validateField(input); + }); + }); + }); + }, + + validateForm(form) { + const inputs = form.querySelectorAll('[required]'); + let isValid = true; + + inputs.forEach(input => { + if (!this.validateField(input)) { + isValid = false; + } + }); + + return isValid; + }, + + validateField(field) { + const value = field.value.trim(); + const type = field.type; + let isValid = true; + + // Remove previous error + field.classList.remove('error'); + const errorMsg = field.parentElement.querySelector('.error-message'); + if (errorMsg) { + errorMsg.remove(); + } + + // Required field + if (field.hasAttribute('required') && !value) { + this.showError(field, 'This field is required'); + return false; + } + + // Email validation + if (type === 'email' && value) { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(value)) { + this.showError(field, 'Please enter a valid email address'); + return false; + } + } + + // Phone validation + if (type === 'tel' && value) { + const phoneRegex = /^[\d\s\-\+\(\)]+$/; + if (!phoneRegex.test(value)) { + this.showError(field, 'Please enter a valid phone number'); + return false; + } + } + + return isValid; + }, + + showError(field, message) { + field.classList.add('error'); + const errorElement = document.createElement('div'); + errorElement.className = 'error-message'; + errorElement.textContent = message; + field.parentElement.appendChild(errorElement); + } +}; + +// Yacht Quick View Modal +const yachtModal = { + init() { + const viewButtons = document.querySelectorAll('.yacht-view-btn'); + viewButtons.forEach(btn => { + btn.addEventListener('click', (e) => { + e.preventDefault(); + this.openModal(); + }); + }); + }, + + openModal() { + // For now, just alert - in production, would open a modal + alert('Quick view modal would open here with yacht details and 360ยฐ view'); + } +}; + +// Parallax Effects +const parallaxEffects = { + init() { + this.elements = document.querySelectorAll('.parallax'); + + if (this.elements.length === 0) return; + + window.addEventListener('scroll', () => { + this.updateParallax(); + }); + }, + + updateParallax() { + const scrolled = window.pageYOffset; + + this.elements.forEach(element => { + const rate = element.dataset.rate || 0.5; + const yPos = -(scrolled * rate); + element.style.transform = `translateY(${yPos}px)`; + }); + } +}; + +// Initialize everything when DOM is ready +document.addEventListener('DOMContentLoaded', () => { + themeManager.init(); + navigation.init(); + smoothScroll.init(); + testimonialSlider.init(); + counterAnimation.init(); + formValidation.init(); + yachtModal.init(); + parallaxEffects.init(); + + // Log successful initialization + console.log('HarborSmith website initialized successfully!'); +}); \ No newline at end of file diff --git a/website-mockups/js/voyage-layout.js b/website-mockups/js/voyage-layout.js new file mode 100644 index 0000000..f1a23eb --- /dev/null +++ b/website-mockups/js/voyage-layout.js @@ -0,0 +1,350 @@ +// Voyage Layout JavaScript - Smooth, Interactive, Engaging + +document.addEventListener('DOMContentLoaded', function() { + // Theme is now gold by default - no switcher needed + + // Navigation scroll effect and logo switching + const nav = document.getElementById('voyageNav'); + const navLogo = document.getElementById('navLogo'); + const navBrandText = document.querySelector('.nav-brand span'); + let lastScroll = 0; + + // Set initial logo state + if (navLogo) { + navLogo.src = 'HARBOR-SMITH-white.png'; + } + + window.addEventListener('scroll', () => { + const currentScroll = window.pageYOffset; + + if (currentScroll > 50) { + nav.classList.add('scrolled'); + // Switch to navy logo when scrolled (white background) + if (navLogo) { + navLogo.src = 'HARBOR-SMITH_navy.png'; + navLogo.alt = 'Harbor Smith Navy Logo'; + } + // Also change text color to match + if (navBrandText) { + navBrandText.style.color = '#1e3a5f'; + } + } else { + nav.classList.remove('scrolled'); + // Switch to white logo when at top (over video) + if (navLogo) { + navLogo.src = 'HARBOR-SMITH-white.png'; + navLogo.alt = 'Harbor Smith White Logo'; + } + // Reset text color to white + if (navBrandText) { + navBrandText.style.color = 'white'; + } + } + + lastScroll = currentScroll; + }); + + // Smooth scroll for navigation links + document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', function(e) { + e.preventDefault(); + const target = document.querySelector(this.getAttribute('href')); + if (target) { + target.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } + }); + }); + + // Parallax effect for hero video + const heroSection = document.getElementById('heroSection'); + const heroVideo = document.querySelector('.hero-video-container'); + + window.addEventListener('scroll', () => { + const scrolled = window.pageYOffset; + const rate = scrolled * 0.5; + + if (heroVideo) { + heroVideo.style.transform = `translateY(${rate}px)`; + } + }); + + // Fleet Carousel + const yachtCards = document.querySelectorAll('.yacht-card'); + const dots = document.querySelectorAll('.dot'); + const prevBtn = document.querySelector('.fleet-prev'); + const nextBtn = document.querySelector('.fleet-next'); + let currentYacht = 0; + + function showYacht(index) { + yachtCards.forEach(card => card.classList.remove('active')); + dots.forEach(dot => dot.classList.remove('active')); + + yachtCards[index].classList.add('active'); + dots[index].classList.add('active'); + } + + if (prevBtn && nextBtn) { + prevBtn.addEventListener('click', () => { + currentYacht = (currentYacht - 1 + yachtCards.length) % yachtCards.length; + showYacht(currentYacht); + }); + + nextBtn.addEventListener('click', () => { + currentYacht = (currentYacht + 1) % yachtCards.length; + showYacht(currentYacht); + }); + } + + dots.forEach((dot, index) => { + dot.addEventListener('click', () => { + currentYacht = index; + showYacht(currentYacht); + }); + }); + + // Auto-rotate fleet carousel + setInterval(() => { + if (document.visibilityState === 'visible') { + currentYacht = (currentYacht + 1) % yachtCards.length; + showYacht(currentYacht); + } + }, 5000); + + // CTA Button interactions + document.querySelectorAll('.btn-primary-warm, .btn-secondary-warm').forEach(btn => { + btn.addEventListener('click', function(e) { + // Create ripple effect + const ripple = document.createElement('span'); + ripple.classList.add('ripple'); + this.appendChild(ripple); + + const rect = this.getBoundingClientRect(); + const size = Math.max(rect.width, rect.height); + const x = e.clientX - rect.left - size / 2; + const y = e.clientY - rect.top - size / 2; + + ripple.style.width = ripple.style.height = size + 'px'; + ripple.style.left = x + 'px'; + ripple.style.top = y + 'px'; + + setTimeout(() => ripple.remove(), 600); + }); + }); + + // Add ripple styles + const style = document.createElement('style'); + style.textContent = ` + .ripple { + position: absolute; + border-radius: 50%; + background: rgba(255, 255, 255, 0.6); + transform: scale(0); + animation: ripple 0.6s ease-out; + pointer-events: none; + } + + @keyframes ripple { + to { + transform: scale(4); + opacity: 0; + } + } + + .btn-primary-warm, + .btn-secondary-warm { + position: relative; + overflow: hidden; + } + `; + document.head.appendChild(style); + + // Animate elements on scroll + const observerOptions = { + threshold: 0.1, + rootMargin: '0px 0px -100px 0px' + }; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.style.opacity = '1'; + entry.target.style.transform = 'translateY(0)'; + } + }); + }, observerOptions); + + // Observe elements for animation + document.querySelectorAll('.welcome-content, .story-card, .booking-card').forEach(el => { + el.style.opacity = '0'; + el.style.transform = 'translateY(30px)'; + el.style.transition = 'all 0.6s ease'; + observer.observe(el); + }); + + // Interactive booking cards + document.querySelectorAll('.booking-card').forEach(card => { + card.addEventListener('mouseenter', function() { + this.style.transform = 'translateY(-10px) scale(1.02)'; + }); + + card.addEventListener('mouseleave', function() { + if (this.classList.contains('featured')) { + this.style.transform = 'scale(1.05)'; + } else { + this.style.transform = 'translateY(0) scale(1)'; + } + }); + }); + + // Phone number click handler + document.querySelectorAll('.btn-booking.primary').forEach(btn => { + if (btn.textContent.includes('415')) { + btn.addEventListener('click', () => { + window.location.href = 'tel:4155550123'; + }); + } + }); + + // Video optimization - pause when not visible + const video = document.querySelector('.hero-video'); + if (video) { + const videoObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + video.play(); + } else { + video.pause(); + } + }); + }, { threshold: 0.25 }); + + videoObserver.observe(video); + } + + // Story cards hover effect + document.querySelectorAll('.story-card').forEach(card => { + const img = card.querySelector('img'); + + card.addEventListener('mouseenter', () => { + if (img) { + img.style.transform = 'scale(1.1)'; + img.style.transition = 'transform 0.6s ease'; + } + }); + + card.addEventListener('mouseleave', () => { + if (img) { + img.style.transform = 'scale(1)'; + } + }); + }); + + // Dynamic trust badge counter - starts from 0 + // Target the white text span specifically (the second span in trust-badge) + const trustBadge = document.querySelector('.trust-badge > span:last-child'); + if (trustBadge) { + let count = 0; + const targetCount = 100; + const duration = 2000; // 2 seconds + const steps = 50; + const increment = targetCount / steps; + const stepDuration = duration / steps; + + // Start with 0 + trustBadge.textContent = `Trusted by 0+ seafarers`; + + const counter = setInterval(() => { + count += increment; + if (count >= targetCount) { + count = targetCount; + clearInterval(counter); + trustBadge.textContent = `Trusted by 100+ seafarers`; + } else { + trustBadge.textContent = `Trusted by ${Math.floor(count)}+ seafarers`; + } + }, stepDuration); + } + + // Remove any duplicate red text that might exist + const allSpans = document.querySelectorAll('.hero-content span'); + allSpans.forEach(span => { + // Check if this is the red duplicate text (not inside trust-badge) + if (span.textContent.includes('Trusted by') && + span.textContent.includes('adventurers') && + !span.closest('.trust-badge')) { + span.style.display = 'none'; + } + }); + + // Smooth hover for navigation links + document.querySelectorAll('.nav-link').forEach(link => { + link.addEventListener('mouseenter', function() { + this.style.transform = 'translateY(-2px)'; + }); + + link.addEventListener('mouseleave', function() { + this.style.transform = 'translateY(0)'; + }); + }); + + // Mobile menu (would need to add hamburger menu for production) + const mobileMenuBtn = document.createElement('button'); + mobileMenuBtn.className = 'mobile-menu-btn'; + mobileMenuBtn.innerHTML = ''; + mobileMenuBtn.style.cssText = ` + display: none; + flex-direction: column; + gap: 4px; + background: transparent; + border: none; + cursor: pointer; + padding: 0.5rem; + `; + + // Add mobile menu styles + const mobileStyles = document.createElement('style'); + mobileStyles.textContent = ` + @media (max-width: 768px) { + .mobile-menu-btn { + display: flex !important; + } + + .mobile-menu-btn span { + width: 24px; + height: 2px; + background: white; + transition: all 0.3s ease; + } + + .voyage-nav.scrolled .mobile-menu-btn span { + background: var(--primary-blue); + } + } + `; + document.head.appendChild(mobileStyles); + + // Add mobile menu button to nav + const navContainer = document.querySelector('.nav-container'); + if (navContainer && window.innerWidth <= 768) { + navContainer.appendChild(mobileMenuBtn); + } + + // Loading animation for images + document.querySelectorAll('img').forEach(img => { + // Check if image is already loaded + if (img.complete && img.naturalHeight !== 0) { + img.style.opacity = '1'; + } else { + img.style.opacity = '0'; + img.addEventListener('load', function() { + this.style.opacity = '1'; + }); + } + img.style.transition = 'opacity 0.6s ease'; + }); + + console.log('Voyage layout initialized successfully!'); +}); \ No newline at end of file diff --git a/website-mockups/kid_1.jpeg b/website-mockups/kid_1.jpeg new file mode 100644 index 0000000..542f685 Binary files /dev/null and b/website-mockups/kid_1.jpeg differ diff --git a/website-mockups/leah_1.jpeg b/website-mockups/leah_1.jpeg new file mode 100644 index 0000000..35eb246 Binary files /dev/null and b/website-mockups/leah_1.jpeg differ diff --git a/website-mockups/logo.jpg b/website-mockups/logo.jpg new file mode 100644 index 0000000..15cedc7 Binary files /dev/null and b/website-mockups/logo.jpg differ diff --git a/website-mockups/maintenance-booking.html b/website-mockups/maintenance-booking.html new file mode 100644 index 0000000..77ea5ed --- /dev/null +++ b/website-mockups/maintenance-booking.html @@ -0,0 +1,884 @@ + + + + + + Schedule Maintenance Service - HarborSmith + + + + + + + + + + + + + + +
+

Schedule Your Service

+

Professional yacht maintenance tailored to your needs

+
+ + +
+
+
+ +
+ +
+

Select Service Type

+
+
+
+ +
+
Routine Maintenance
+
+ Regular scheduled service to keep your yacht in top condition +
+
+
+
+ +
+
Repairs & Fixes
+
+ Address specific issues or problems with your vessel +
+
+
+
+ +
+
Inspection Only
+
+ Comprehensive inspection with detailed report +
+
+
+
+ +
+
Emergency Service
+
+ Urgent repairs needed as soon as possible +
+
+
+
+ + +
+

Select Specific Services Needed

+
+ + + + + + + + +
+
+ + +
+

Vessel Information

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ + +
+ +
+
+ +
+

+ Click to upload photos +

+

+ Upload photos of any damage or areas of concern +

+ +
+
+
+ + +
+

Service Urgency

+
+
+
Routine Service
+
Schedule within 2 weeks
+
+
+
Soon
+
Within 1 week
+
+
+
Urgent
+
Within 48 hours
+
+
+
Emergency
+
Need help today
+
+
+ +
+

Preferred Service Date

+
+ + +
+
+ +
+
8:00 AM
+
9:00 AM
+
10:00 AM
+
11:00 AM
+
12:00 PM
+
1:00 PM
+
2:00 PM
+
3:00 PM
+
+
+
+
+ + +
+

Contact Information

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+ + + +
+
+
+ + +
+ + +
+
+ + + +
+
+
+ + +
+ +
+ + + + \ No newline at end of file diff --git a/website-mockups/maintenance-dashboard-final-2025-09-02T21-11-26-384Z.png b/website-mockups/maintenance-dashboard-final-2025-09-02T21-11-26-384Z.png new file mode 100644 index 0000000..0b41c6c Binary files /dev/null and b/website-mockups/maintenance-dashboard-final-2025-09-02T21-11-26-384Z.png differ diff --git a/website-mockups/maintenance-documents-final-2025-09-02T21-12-54-037Z.png b/website-mockups/maintenance-documents-final-2025-09-02T21-12-54-037Z.png new file mode 100644 index 0000000..7ffb93e Binary files /dev/null and b/website-mockups/maintenance-documents-final-2025-09-02T21-12-54-037Z.png differ diff --git a/website-mockups/maintenance-invoices-final-2025-09-02T21-14-02-273Z.png b/website-mockups/maintenance-invoices-final-2025-09-02T21-14-02-273Z.png new file mode 100644 index 0000000..5f7f136 Binary files /dev/null and b/website-mockups/maintenance-invoices-final-2025-09-02T21-14-02-273Z.png differ diff --git a/website-mockups/maintenance-reports-final-2025-09-02T21-13-25-803Z.png b/website-mockups/maintenance-reports-final-2025-09-02T21-13-25-803Z.png new file mode 100644 index 0000000..5929923 Binary files /dev/null and b/website-mockups/maintenance-reports-final-2025-09-02T21-13-25-803Z.png differ diff --git a/website-mockups/maintenance-schedule-final-2025-09-02T21-12-26-333Z.png b/website-mockups/maintenance-schedule-final-2025-09-02T21-12-26-333Z.png new file mode 100644 index 0000000..9f60f1c Binary files /dev/null and b/website-mockups/maintenance-schedule-final-2025-09-02T21-12-26-333Z.png differ diff --git a/website-mockups/maintenance-vessels-final-2025-09-02T21-11-55-149Z.png b/website-mockups/maintenance-vessels-final-2025-09-02T21-11-55-149Z.png new file mode 100644 index 0000000..0b41c6c Binary files /dev/null and b/website-mockups/maintenance-vessels-final-2025-09-02T21-11-55-149Z.png differ diff --git a/website-mockups/maintenance.html b/website-mockups/maintenance.html new file mode 100644 index 0000000..12a2f55 --- /dev/null +++ b/website-mockups/maintenance.html @@ -0,0 +1,818 @@ + + + + + + Yacht Maintenance Services - HarborSmith + + + + + + + + + + + + + +
+
+

Expert Yacht Maintenance

+

Keep your vessel in pristine condition with our professional maintenance services

+
+ + + +
+
+
+ + +
+
+
+

Comprehensive Maintenance Services

+

From routine care to major repairs, we've got you covered

+
+ +
+ +
+
+ +
+

Regular Maintenance

+

+ Keep your yacht running smoothly with our scheduled maintenance programs +

+
+
+ + Oil changes & filter replacements +
+
+ + Belt & hose inspections +
+
+ + Battery maintenance +
+
+ + Fluid level checks +
+
+ +
+ + +
+
+ +
+

Engine Services

+

+ Complete engine diagnostics, repairs, and rebuilds by certified technicians +

+
+
+ + Engine diagnostics +
+
+ + Fuel system service +
+
+ + Cooling system repairs +
+
+ + Transmission service +
+
+ +
+ + +
+
+ +
+

Hull & Bottom Services

+

+ Professional hull cleaning, painting, and bottom maintenance +

+
+
+ + Bottom cleaning +
+
+ + Anti-fouling paint +
+
+ + Zinc replacement +
+
+ + Gel coat repairs +
+
+ +
+ + +
+
+ +
+

Electrical Systems

+

+ Complete electrical system maintenance and upgrades +

+
+
+ + Wiring inspections +
+
+ + Battery installation +
+
+ + Navigation electronics +
+
+ + Solar panel installation +
+
+ +
+ + +
+
+ +
+

Interior & Upholstery

+

+ Interior refurbishment, upholstery repair, and detailing services +

+
+
+ + Upholstery cleaning +
+
+ + Canvas & cover repairs +
+
+ + Carpet replacement +
+
+ + Wood refinishing +
+
+ +
+ + +
+
+ +
+

Emergency Repairs

+

+ 24/7 emergency repair service to get you back on the water fast +

+
+
+ + 24/7 availability +
+
+ + Mobile service +
+
+ + Towing assistance +
+
+ + On-water repairs +
+
+ +
+
+
+
+ + +
+
+
+

Transparent Pricing

+

Clear, competitive rates for all services

+
+ +
+
+

Service Pricing Guide

+
+
+
Basic Maintenance Package
+
Monthly
+
From $299
+
+
+
Engine Service
+
Per Service
+
From $450
+
+
+
Hull Cleaning
+
Per Cleaning
+
From $200
+
+
+
Bottom Paint
+
Per Application
+
From $1,500
+
+
+
Electrical Diagnostics
+
Per Hour
+
$125
+
+
+
Emergency Service
+
Per Call
+
From $350
+
+
+ +

+ * Prices vary based on vessel size and specific requirements. Contact us for a detailed quote. +

+
+
+ + +
+
+
+

How It Works

+

Simple steps to professional yacht maintenance

+
+ +
+
+
1
+

Request Service

+

Fill out our online form or call us to schedule

+
+
+
2
+

Free Inspection

+

We assess your yacht and provide a detailed quote

+
+
+
3
+

Professional Service

+

Our certified technicians complete the work

+
+
+
4
+

Quality Check

+

We ensure everything meets our high standards

+
+
+
+
+ + +
+
+

Certified Excellence

+

Our technicians are certified by leading marine organizations

+
+ + + + + +
+
+
+ + +
+
+

24/7 Emergency Service

+

Engine failure? Taking on water? We're here to help, day or night.

+ +
+
+ + +
+
+
+

Existing Clients - Access Your Portal

+

+ Manage your vessels, schedule services, view reports, and pay invoices online +

+ +
+
+ + + +

Client Portal

+

Access your maintenance dashboard

+ +
+ +
+ + + +

View Demo

+

See the portal in action

+ +
+ +
+ + + +

New Client?

+

Register for portal access

+ +
+
+
+
+
+ + +
+
+
+

Ready to Schedule Your Service?

+

+ Keep your yacht in perfect condition with regular professional maintenance +

+ +
+
+ + + +

Schedule Service

+

Book your maintenance online

+ +
+ + + +
+ + + +

Get a Quote

+

Free estimates for all services

+ +
+
+
+
+
+ + +
+ +
+ + + + \ No newline at end of file diff --git a/website-mockups/maintenance/maintenance-dashboard.html b/website-mockups/maintenance/maintenance-dashboard.html new file mode 100644 index 0000000..be531b5 --- /dev/null +++ b/website-mockups/maintenance/maintenance-dashboard.html @@ -0,0 +1,405 @@ + + + + + + Maintenance Dashboard - HarborSmith Portal + + + + + + + + + + +
+ + + + +
+ +
+
+ Maintenance Portal + / + Dashboard +
+ +
+ + + + +
+
JS
+
+
John Smith
+
Maintenance Client
+
+
+
+
+ + +
+ +
+

Welcome back, John!

+

Here's an overview of your fleet maintenance status

+
+ + +
+
+
+

Active Vessels

+
+ +
+
+
3
+
+ + +1 this month +
+
+ +
+
+

Upcoming Services

+
+ +
+
+
2
+

Next: Dec 15, 2024

+
+ +
+
+

Open Invoices

+
+ +
+
+
$4,250
+ 1 overdue +
+ +
+
+

Service Score

+
+ +
+
+
98%
+
+ + Excellent +
+
+
+ + +
+ +
+
+

Upcoming Services

+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VesselServiceDateStatus
Sea BreezeAnnual InspectionDec 15, 2024Scheduled
Wave RunnerEngine ServiceDec 22, 2024Confirmed
Ocean SpiritHull CleaningJan 5, 2025Scheduled
+
+ + +
+

Recent Activity

+
+
+
+
+
2 hours ago
+

Invoice #1247 paid

+

Payment received for Wave Runner engine service

+
+
+
+
+
+
Yesterday
+

Service report uploaded

+

Sea Breeze monthly maintenance complete

+
+
+
+
+
+
3 days ago
+

New document added

+

Insurance renewal certificate for Ocean Spirit

+
+
+
+
+
+ + +
+
+

Fleet Overview

+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Vessel NameTypeLast ServiceNext ServiceHealth ScoreActions
+
+ Sea Breeze +
+ Sea Breeze +
2019 Beneteau 45
+
+
+
SailboatNov 10, 2024Dec 15, 2024 +
+
+
+
+ 95% +
+
+ +
+
+ Wave Runner +
+ Wave Runner +
2021 Sea Ray 350
+
+
+
Motor YachtOct 28, 2024Dec 22, 2024 +
+
+
+
+ 88% +
+
+ +
+
+ Ocean Spirit +
+ Ocean Spirit +
2020 Catalina 425
+
+
+
SailboatNov 5, 2024Jan 5, 2025 +
+
+
+
+ 92% +
+
+ +
+
+ + +
+

Quick Actions

+
+ + + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/website-mockups/maintenance/maintenance-documents.html b/website-mockups/maintenance/maintenance-documents.html new file mode 100644 index 0000000..de8e755 --- /dev/null +++ b/website-mockups/maintenance/maintenance-documents.html @@ -0,0 +1,415 @@ + + + + + + Documents - HarborSmith Portal + + + + + + + +
+ + + + +
+ +
+ + +
+ + +
+ + + + 3 +
+ + +
+
+ + +
+ + +
+ +
+ +
+

Categories

+
+
+ + + +
+ All Documents + 24 +
+
+
+ + + +
+ Registration + 3 +
+
+
+ + + +
+ Insurance + 4 +
+
+
+ + + +
+ Warranties + 6 +
+
+
+ + + +
+ Safety Certificates + 5 +
+
+
+ + + +
+ Owner Manuals + 8 +
+
+
+ + + + +
+ Service History + 12 +
+
+ + +
+
+ + + +

Drop files here to upload

+

or click to browse files

+

Supported formats: PDF, JPG, PNG, DOC, DOCX (Max 10MB)

+
+
+
+ + +
+ +
+
+ +
+ +
+ + +
+
+
+ + + +
+
+
Vessel Registration - Sea Breeze
+
+ 2.3 MB + โ€ข + Uploaded Nov 15, 2024 +
+
+
Valid until Dec 2025
+
+ + + +
+
+ +
+
+ + + +
+
+
Insurance Policy - Marine Coverage
+
+ 1.8 MB + โ€ข + Uploaded Oct 20, 2024 +
+
+
Expires in 30 days
+
+ + + +
+
+ +
+
+ + + +
+
+
Safety Equipment Certificate
+
+ 856 KB + โ€ข + Uploaded Sep 5, 2024 +
+
+
Valid until Jun 2025
+
+ + + +
+
+ +
+
+ + + +
+
+
Engine Warranty Documentation
+
+ 524 KB + โ€ข + Uploaded Aug 12, 2024 +
+
+
Valid until 2026
+
+ + + +
+
+ +
+
+ + + +
+
+
Radio License
+
+ 342 KB + โ€ข + Uploaded Jun 15, 2024 +
+
+
Expired
+
+ + + +
+
+
+
+
+
+
+
+ + diff --git a/website-mockups/maintenance/maintenance-invoices.html b/website-mockups/maintenance/maintenance-invoices.html new file mode 100644 index 0000000..b03199c --- /dev/null +++ b/website-mockups/maintenance/maintenance-invoices.html @@ -0,0 +1,391 @@ + + + + + + Invoices - HarborSmith Portal + + + + + + + +
+ + + + +
+ +
+ + +
+ + +
+ + + + 3 +
+ + +
+
+ + +
+ + + +
+
+
$12,450
+
Paid This Year
+
+
+
$2,850
+
Pending Payment
+
+
+
$0
+
Overdue
+
+
+
18
+
Total Invoices
+
+
+ + +
+
+

๐Ÿ”„ Auto-Pay Settings

+
+
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+ + + +
+ +
+ + +
+
+
+
+
+
INV-2024-1247
+
Sea Breeze
+
+ Pending +
+
+
+ Date + Nov 15, 2024 +
+
+ Service + Annual Maintenance +
+
+ Amount + $2,850.00 +
+
+ Due Date + Nov 30, 2024 +
+
+
+
+ + +
+
+ +
+
+
+
+
INV-2024-1235
+
Wave Runner
+
+ +
+
+
+ Date + Oct 28, 2024 +
+
+ Service + Hull Cleaning +
+
+ Amount + $450.00 +
+
+ Paid On + Oct 30, 2024 +
+
+
+
+ + +
+
+ +
+
+
+
+
INV-2024-1221
+
Sea Breeze
+
+ +
+
+
+ Date + Oct 10, 2024 +
+
+ Service + Engine Service +
+
+ Amount + $1,200.00 +
+
+ Paid On + Oct 12, 2024 +
+
+
+
+ + +
+
+ +
+
+
+
+
INV-2024-1198
+
Ocean Spirit
+
+ +
+
+
+ Date + Sep 15, 2024 +
+
+ Service + Electronics Update +
+
+ Amount + $3,200.00 +
+
+ Paid On + Sep 18, 2024 +
+
+
+
+ + +
+
+
+
+
+
+ + diff --git a/website-mockups/maintenance/maintenance-reports.html b/website-mockups/maintenance/maintenance-reports.html new file mode 100644 index 0000000..fd67fc1 --- /dev/null +++ b/website-mockups/maintenance/maintenance-reports.html @@ -0,0 +1,579 @@ + + + + + + Service Reports - HarborSmith Portal + + + + + + + + + + + +
+ + + + +
+ +
+
+ Maintenance Portal + / + Service Reports +
+ +
+ + + + +
+
JS
+
+
John Smith
+
Maintenance Client
+
+
+
+
+ + +
+ + + +
+
+
24
+
Total Reports
+
+
+
18
+
Completed Services
+
+
+
3
+
Pending Reviews
+
+
+
98%
+
Satisfaction Rate
+
+
+ + +
+ + + + +
+ + +
+
+ + +
+ +
+ + +
+ +
+
+
+

Annual Inspection

+

Sea Breeze

+
+ Completed +
+
+
+ Report # + SR-2024-089 +
+
+ Date + Nov 10, 2024 +
+
+ Technician + Mike Johnson +
+
+ Duration + 5.5 hours +
+
+
+ + +
+
+ + +
+
+
+

Engine Service

+

Wave Runner

+
+ Completed +
+
+
+ Report # + SR-2024-087 +
+
+ Date + Oct 28, 2024 +
+
+ Technician + Sarah Williams +
+
+ Duration + 3.5 hours +
+
+
+ + +
+
+ + +
+
+
+

Hull Cleaning

+

Ocean Spirit

+
+ Pending Review +
+
+
+ Report # + SR-2024-086 +
+
+ Date + Nov 5, 2024 +
+
+ Technician + Tom Davis +
+
+ Duration + 2.5 hours +
+
+
+ + +
+
+ + +
+
+
+

Electronics Check

+

Sea Breeze

+
+ Completed +
+
+
+ Report # + SR-2024-085 +
+
+ Date + Oct 15, 2024 +
+
+ Technician + Tom Davis +
+
+ Duration + 4.0 hours +
+
+
+ + +
+
+ + +
+
+
+

Quarterly Maintenance

+

Wave Runner

+
+ In Progress +
+
+
+ Report # + SR-2024-090 +
+
+ Date + Dec 22, 2024 +
+
+ Technician + Mike Johnson +
+
+ Status + Scheduled +
+
+
+ + +
+
+ + +
+
+
+

Safety Equipment

+

Ocean Spirit

+
+ Completed +
+
+
+ Report # + SR-2024-084 +
+
+ Date + Sep 20, 2024 +
+
+ Technician + Sarah Williams +
+
+ Duration + 2.0 hours +
+
+
+ + +
+
+
+ + +
+ + + + + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/website-mockups/maintenance/maintenance-schedule.html b/website-mockups/maintenance/maintenance-schedule.html new file mode 100644 index 0000000..e30f035 --- /dev/null +++ b/website-mockups/maintenance/maintenance-schedule.html @@ -0,0 +1,507 @@ + + + + + + Schedule Service - HarborSmith Portal + + + + + + + + + + + +
+ + + + +
+ +
+
+ Maintenance Portal + / + Schedule Service +
+ +
+ + + + +
+
JS
+
+
John Smith
+
Maintenance Client
+
+
+
+
+ + +
+ + + +
+

+ + Select Vessel & Service Type +

+ +
+
+ + +
+ +
+ + +
+
+ +

Service Type

+
+
+ +
Annual Maintenance
+

Complete inspection

+
+
+ +
Engine Service
+

Engine maintenance

+
+
+ +
Hull Cleaning
+

Bottom cleaning

+
+
+ +
Electronics
+

System check

+
+
+
+ + +
+
+
+

Select Date

+
+ + + +
+
+ +

December 2024

+ +
+
Sun
+
Mon
+
Tue
+
Wed
+
Thu
+
Fri
+
Sat
+ +
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
+
+ +
+

Available Time Slots - Dec 9

+
+
8:00 AM
+
10:00 AM
+
12:00 PM
+
2:00 PM
+
4:00 PM
+
6:00 PM
+
+
+
+ + +
+

+ + Technician Preference +

+ +
+
+
Any Available
+

First available tech

+
+
+
Mike Johnson
+
โ˜…โ˜…โ˜…โ˜…โ˜…
+

Your regular technician

+
+
+
Sarah Williams
+
โ˜…โ˜…โ˜…โ˜…โ˜…
+

Engine specialist

+
+
+ +
+ + +
+ +
+ + + +
+
+ + +
+

+ + Booking Summary +

+ +
+
+ Vessel: + Sea Breeze - 2019 Beneteau 45 +
+
+ Service: + Annual Maintenance +
+
+ Date: + December 9, 2024 +
+
+ Time: + 10:00 AM +
+
+ Technician: + Mike Johnson +
+
+ Estimated Duration: + 4-6 hours +
+
+ Estimated Cost: + $1,200 - $1,500 +
+
+ +
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/website-mockups/maintenance/maintenance-vessels.html b/website-mockups/maintenance/maintenance-vessels.html new file mode 100644 index 0000000..b3e6b00 --- /dev/null +++ b/website-mockups/maintenance/maintenance-vessels.html @@ -0,0 +1,418 @@ + + + + + + My Vessels - HarborSmith Portal + + + + + + + + + + +
+ + + + +
+ +
+
+ Maintenance Portal + / + My Vessels +
+ +
+ + + + +
+
JS
+
+
John Smith
+
Maintenance Client
+
+
+
+
+ + +
+ +
+

My Vessels

+

Manage your fleet and vessel information

+
+ + +
+
+ + +
+ +
+ + +
+ +
+
+ Sea Breeze + Active +
+ +

Sea Breeze

+

2019 Beneteau Oceanis 45

+ +
+
+
Registration
+
CA-1234-SB
+
+
+
Length
+
45 ft
+
+
+
Last Service
+
Nov 10, 2024
+
+
+
Next Service
+
Dec 15, 2024
+
+
+ +
+
+
+
+ 95% Health +
+ +
+ + +
+
+ + +
+
+ Wave Runner + Active +
+ +

Wave Runner

+

2021 Sea Ray Sundancer 350

+ +
+
+
Registration
+
CA-5678-WR
+
+
+
Length
+
35 ft
+
+
+
Last Service
+
Oct 28, 2024
+
+
+
Next Service
+
Dec 22, 2024
+
+
+ +
+
+
+
+ 88% Health +
+ +
+ + +
+
+ + +
+
+ Ocean Spirit + Active +
+ +

Ocean Spirit

+

2020 Catalina 425

+ +
+
+
Registration
+
CA-9012-OS
+
+
+
Length
+
42.5 ft
+
+
+
Last Service
+
Nov 5, 2024
+
+
+
Next Service
+
Jan 5, 2025
+
+
+ +
+
+
+
+ 92% Health +
+ +
+ + +
+
+ + +
+ +

Add New Vessel

+

Register a new vessel for maintenance services

+
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/website-mockups/portal-login.html b/website-mockups/portal-login.html new file mode 100644 index 0000000..ddfe17c --- /dev/null +++ b/website-mockups/portal-login.html @@ -0,0 +1,479 @@ + + + + + + HarborSmith Portal - Login + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/website-mockups/public/diver.jpg b/website-mockups/public/diver.jpg new file mode 100644 index 0000000..c87c76c Binary files /dev/null and b/website-mockups/public/diver.jpg differ diff --git a/website-mockups/public/golden_gate.jpg b/website-mockups/public/golden_gate.jpg new file mode 100644 index 0000000..ec49660 Binary files /dev/null and b/website-mockups/public/golden_gate.jpg differ diff --git a/website-mockups/public/logo.jpg b/website-mockups/public/logo.jpg new file mode 100644 index 0000000..15cedc7 Binary files /dev/null and b/website-mockups/public/logo.jpg differ diff --git a/website-mockups/public/yacht_party_couple_holding_hands.jpg b/website-mockups/public/yacht_party_couple_holding_hands.jpg new file mode 100644 index 0000000..ad596be Binary files /dev/null and b/website-mockups/public/yacht_party_couple_holding_hands.jpg differ diff --git a/website-mockups/sausalito-boat-show-2024.jpg b/website-mockups/sausalito-boat-show-2024.jpg new file mode 100644 index 0000000..a15c52e Binary files /dev/null and b/website-mockups/sausalito-boat-show-2024.jpg differ diff --git a/website-mockups/sf_bay_exposure.jpg b/website-mockups/sf_bay_exposure.jpg new file mode 100644 index 0000000..750571c Binary files /dev/null and b/website-mockups/sf_bay_exposure.jpg differ diff --git a/website-mockups/yacht_party_couple_holding_hands.jpg b/website-mockups/yacht_party_couple_holding_hands.jpg new file mode 100644 index 0000000..ad596be Binary files /dev/null and b/website-mockups/yacht_party_couple_holding_hands.jpg differ