Initial import of HarborSmith website
Some checks failed
build-website / build (push) Failing after 1m2s

This commit is contained in:
2025-09-18 22:20:01 +02:00
commit ec72c5d62b
168 changed files with 65020 additions and 0 deletions

49
.dockerignore Normal file
View File

@@ -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

View File

@@ -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

34
.gitignore vendored Normal file
View File

@@ -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

662
CLAUDE.md Normal file
View File

@@ -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=<secure_password>
# External MinIO (already running separately)
MINIO_ENDPOINT=<minio_host>
MINIO_PORT=9000
MINIO_USE_SSL=true
MINIO_ACCESS_KEY=<access_key>
MINIO_SECRET_KEY=<secret_key>
# Authentication
KEYCLOAK_URL=https://auth.harborsmith.com
KEYCLOAK_REALM=harborsmith
KEYCLOAK_CLIENT_ID=harborsmith-webapp
# Payments
STRIPE_SECRET_KEY=sk_live_<key>
CAL_API_KEY=cal_live_<key>
```
## 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.**

4549
HARBORSMITH_ARCHITECTURE.md Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

402
NAUTICAL_DESIGN_SYSTEM.md Normal file
View File

@@ -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
<template>
<!-- Semantic HTML -->
</template>
<script setup lang="ts">
// TypeScript logic
</script>
<style scoped>
/* Component-specific styles */
</style>
```
### 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
<ThemedButton variant="primary" size="lg">
Book Charter
<Icon name="lucide:arrow-right" class="ml-2" />
</ThemedButton>
```
### Card Component
```vue
<VesselCard
:image="yacht.image"
:title="yacht.name"
:price="yacht.price"
:capacity="yacht.capacity"
@click="selectVessel"
/>
```
### Typography
```vue
<h1 class="font-display text-5xl font-bold text-navy">
Your Adventure Awaits
</h1>
<p class="text-lg text-gray-600 leading-relaxed">
Experience luxury yacht charters in the San Francisco Bay
</p>
```
---
This design system ensures consistency, accessibility, and performance across the HarborSmith platform while maintaining the premium nautical aesthetic that defines the brand.

24
apps/website/.gitignore vendored Normal file
View File

@@ -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

38
apps/website/Dockerfile Normal file
View File

@@ -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;"]

75
apps/website/README.md Normal file
View File

@@ -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.

View File

@@ -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;
}
}

View File

@@ -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);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
<template>
<footer class="voyage-footer" id="contact">
<div class="footer-container">
<div class="footer-content">
<div class="footer-brand">
<img src="/HARBOR-SMITH-white.png" alt="Harbor Smith" class="footer-logo">
<h3>HARBOR SMITH</h3>
<p>Your trusted partner for professional boat maintenance in the San Francisco Bay Area</p>
<div class="social-links">
<a href="#" aria-label="Facebook"><LucideFacebook /></a>
<a href="#" aria-label="Instagram"><LucideInstagram /></a>
<a href="#" aria-label="Twitter"><LucideTwitter /></a>
</div>
</div>
<div class="footer-contact">
<h4>Get in Touch</h4>
<p><LucideMapPin class="footer-icon" /> San Francisco Bay Area</p>
<p><LucidePhone class="footer-icon" /> (510) 701-2535</p>
<p><LucideMail class="footer-icon" /> hello@harborsmith.co</p>
<p><LucideClock class="footer-icon" /> Mobile Service Available 7 Days</p>
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2025 Harbor Smith Boat Maintenance Services. All rights reserved.</p>
</div>
</div>
</footer>
</template>
<script setup lang="ts">
// Footer uses globally registered Lucide icons
</script>
<style scoped>
/* Footer layout handled by voyage-layout.css */
</style>

View File

@@ -0,0 +1,85 @@
<template>
<nav
id="voyageNav"
:class="['voyage-nav', { scrolled: isScrolled }]"
>
<div class="nav-container">
<div class="nav-brand">
<img
:src="navLogo"
:alt="logoAlt"
class="nav-logo"
id="navLogo"
>
<span>HARBOR SMITH</span>
</div>
<div class="nav-links">
<a
v-for="link in navLinks"
:key="link.href"
:href="link.href"
class="nav-link"
@click="handleSmoothScroll($event, link.href)"
>
{{ link.label }}
</a>
<a
href="tel:510-701-2535"
class="nav-link nav-cta"
>
Call Now
</a>
</div>
</div>
</nav>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
const isScrolled = ref(false)
const navLinks = [
{ href: '#services', label: 'Services' },
{ href: '#testimonials', label: 'Testimonials' },
{ href: '#contact', label: 'Contact' }
]
const navLogo = computed(() =>
isScrolled.value ? '/HARBOR-SMITH_navy.png' : '/HARBOR-SMITH-white.png'
)
const logoAlt = computed(() =>
isScrolled.value ? 'Harbor Smith Navy Logo' : 'Harbor Smith White Logo'
)
const handleScroll = () => {
isScrolled.value = window.pageYOffset > 50
}
const handleSmoothScroll = (event: Event, href: string) => {
if (!href.startsWith('#')) {
return
}
const target = document.querySelector(href)
if (!target) {
return
}
event.preventDefault()
target.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
onMounted(() => {
handleScroll()
window.addEventListener('scroll', handleScroll, { passive: true })
})
onBeforeUnmount(() => {
window.removeEventListener('scroll', handleScroll)
})
</script>
<style scoped>
/* Navigation styling handled in voyage-layout.css */
</style>

View File

@@ -0,0 +1,59 @@
<template>
<section class="booking-cta" style="background: linear-gradient(135deg, rgba(30, 58, 95, 0.9), rgba(75, 124, 184, 0.85)), url('sf_bay_exposure.jpg') center/cover no-repeat; position: relative;">
<div class="booking-container">
<div class="booking-content">
<h2 class="booking-title">Ready to Schedule Your Service?</h2>
<p class="booking-subtitle">
Join hundreds of boat owners who trust Harbor Smith for professional maintenance
</p>
<div class="booking-options">
<div class="booking-card">
<span class="booking-icon">
<LucideCalendar />
</span>
<h3>Schedule Service</h3>
<p>Book your maintenance appointment</p>
<button class="btn-booking" @click="handleBookNow">Book Now</button>
</div>
<div class="booking-card featured">
<span class="booking-icon">
<LucidePhone />
</span>
<h3>Call Us Today</h3>
<p>Get a personalized quote</p>
<button class="btn-booking primary" @click="handleCall">Call (510) 701-2535</button>
</div>
<div class="booking-card">
<span class="booking-icon">
<LucideMail />
</span>
<h3>Email Us</h3>
<p>Send us your service request</p>
<button class="btn-booking" @click="handleEmail">Contact Us</button>
</div>
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
const handleBookNow = () => {
window.location.href = 'maintenance-booking.html'
}
const handleCall = () => {
window.location.href = 'tel:510-701-2535'
}
const handleEmail = () => {
window.location.href = 'mailto:hello@harborsmith.co'
}
</script>
<style scoped>
/* Booking styles defined in voyage-layout.css */
</style>

View File

@@ -0,0 +1,56 @@
<template>
<section class="gallery-section">
<div class="container">
<div class="section-header">
<h2 class="section-title">Our Work in Action</h2>
<p class="section-subtitle">Professional maintenance services delivered with care</p>
</div>
<div class="image-gallery">
<div class="gallery-item large">
<img src="/diver_cleaning_2.jpg" alt="Professional hull cleaning">
<div class="gallery-overlay">
<span class="gallery-caption">Expert Hull Cleaning</span>
</div>
</div>
<div class="gallery-item">
<img src="/ExtCleaning.jpg" alt="Exterior cleaning service">
<div class="gallery-overlay">
<span class="gallery-caption">Detailed Cleaning</span>
</div>
</div>
<div class="gallery-item">
<img src="/Washdown2.jpg" alt="Professional washdown">
<div class="gallery-overlay">
<span class="gallery-caption">Thorough Washdown</span>
</div>
</div>
<div class="gallery-item">
<img src="/Helm.jpg" alt="Interior maintenance">
<div class="gallery-overlay">
<span class="gallery-caption">Interior Care</span>
</div>
</div>
<div class="gallery-item">
<img src="/Foredeck.jpg" alt="Deck maintenance">
<div class="gallery-overlay">
<span class="gallery-caption">Deck Service</span>
</div>
</div>
<div class="gallery-item">
<img src="/Waxing.jpg" alt="Boat waxing service">
<div class="gallery-overlay">
<span class="gallery-caption">Protective Waxing</span>
</div>
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
// Static gallery content driven by voyage-layout styles
</script>
<style scoped>
/* Gallery styling provided by voyage-layout.css */
</style>

View File

@@ -0,0 +1,148 @@
<template>
<section class="hero-voyage" id="heroSection">
<div ref="videoContainer" class="hero-video-container">
<video
ref="videoElement"
autoplay
loop
muted
playsinline
class="hero-video"
@loadeddata="handleVideoLoaded"
>
<source
src="https://videos.pexels.com/video-files/3571264/3571264-uhd_2560_1440_30fps.mp4"
type="video/mp4"
>
</video>
<div
v-if="!videoLoaded"
class="hero-image-fallback"
:style="{ backgroundImage: 'url(/golden_gate.jpg)' }"
/>
<div class="hero-overlay gradient-warm" />
<div class="hero-overlay gradient-depth" />
</div>
<div class="hero-content">
<div class="hero-logo animate-fade-in">
<img src="/HARBOR-SMITH-white.png" alt="Harbor Smith" style="height: 250px; margin: 40px 0;">
</div>
<div class="trust-badge animate-fade-in">
<div class="stars">
<span class="stars-icons">
<LucideStar class="star-filled" />
<LucideStar class="star-filled" />
<LucideStar class="star-filled" />
<LucideStar class="star-filled" />
<LucideStar class="star-filled" />
</span>
</div>
<span>Trusted by 0+ seafarers</span>
</div>
<p class="hero-subtext animate-fade-up-delay">
<span style="font-size: 1.5rem; font-weight: 500; text-transform: none; letter-spacing: normal; margin-bottom: 10px; display: block;">
Personalized Service Maintenance for Your Boat
</span>
Keep your vessel pristine with San Francisco Bay's premier mobile boat maintenance service.
</p>
<div class="hero-actions animate-fade-up-delay-2">
<button class="btn-primary-warm" @click="handlePhoneClick">
<LucidePhone class="btn-icon" />
Call (510) 701-2535
</button>
<button class="btn-secondary-warm" @click="handleServicesClick">
<LucideWrench class="btn-icon" />
View Our Services
</button>
</div>
<div class="scroll-indicator">
<span>Scroll to explore</span>
<div class="scroll-arrow">
<LucideChevronDown />
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useParallax } from '~/composables/useParallax'
import { useIntersectionAnimations } from '~/composables/useIntersectionAnimations'
import { useRipple } from '~/composables/useRipple'
const videoLoaded = ref(false)
const videoContainer = ref<HTMLElement | null>(null)
const videoElement = ref<HTMLVideoElement | null>(null)
useParallax(videoContainer, 0.5)
useIntersectionAnimations()
useRipple()
const handleVideoLoaded = () => {
videoLoaded.value = true
}
const handlePhoneClick = () => {
window.location.href = 'tel:510-701-2535'
}
const handleServicesClick = () => {
const element = document.querySelector('#services')
if (element) {
element.scrollIntoView({ behavior: 'smooth' })
}
}
const handleVideoVisibility = () => {
if (!videoElement.value) {
return
}
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
videoElement.value?.play()
} else {
videoElement.value?.pause()
}
})
}, { threshold: 0.25 })
observer.observe(videoElement.value)
return () => observer.disconnect()
}
const initSmoothScroll = () => {
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
anchor.addEventListener('click', (event) => {
event.preventDefault()
const href = anchor.getAttribute('href')
if (!href) {
return
}
const target = document.querySelector(href)
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
})
})
}
onMounted(() => {
handleVideoVisibility()
initSmoothScroll()
})
</script>
<style scoped>
/* Additional animations defined in voyage-layout.css */
</style>

View File

@@ -0,0 +1,94 @@
<template>
<section class="fleet-showcase" id="services">
<div class="container">
<div class="section-header">
<h2 class="section-title">Our Premium Services</h2>
<p class="section-subtitle">Professional boat maintenance tailored to your needs</p>
</div>
<div class="services-grid" style="display: flex; flex-wrap: wrap; gap: 30px; max-width: 1200px; margin: 0 auto; justify-content: center;">
<div class="service-card" style="background: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); padding: 0; overflow: hidden; text-align: center; flex: 0 1 350px; min-width: 280px;">
<div style="width: 100%; height: 200px; overflow: hidden;">
<img src="/diver_cleaning.jpg" alt="Professional hull cleaning service" style="width: 100%; height: 100%; object-fit: cover;">
</div>
<div style="padding: 30px;">
<h3 style="font-size: 24px; margin-bottom: 15px; color: #1e3a5f;">Hull Cleaning</h3>
<p style="color: #666; margin-bottom: 20px;">
Professional underwater hull cleaning to maintain your boat's performance and fuel efficiency.
</p>
<ul style="list-style: none; padding: 0; margin: 20px 0; text-align: left;">
<li style="padding: 8px 0;"><LucideCheck class="spec-icon" /> Removes marine growth</li>
<li style="padding: 8px 0;"><LucideCheck class="spec-icon" /> Improves fuel efficiency</li>
<li style="padding: 8px 0;"><LucideCheck class="spec-icon" /> Extends hull life</li>
</ul>
<button class="btn-primary-warm" style="width: 100%;" @click="handleQuote">Get Quote</button>
</div>
</div>
<div class="service-card" style="background: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); padding: 0; overflow: hidden; text-align: center; flex: 0 1 350px; min-width: 280px;">
<div style="width: 100%; height: 200px; overflow: hidden;">
<img src="/Washdown.jpg" alt="Professional boat wash and wax service" style="width: 100%; height: 100%; object-fit: cover;">
</div>
<div style="padding: 30px;">
<h3 style="font-size: 24px; margin-bottom: 15px; color: #1e3a5f;">Exterior Wash &amp; Wax</h3>
<p style="color: #666; margin-bottom: 20px;">
Complete exterior detailing to keep your boat looking pristine and protected.
</p>
<ul style="list-style: none; padding: 0; margin: 20px 0; text-align: left;">
<li style="padding: 8px 0;"><LucideCheck class="spec-icon" /> Deep cleaning wash</li>
<li style="padding: 8px 0;"><LucideCheck class="spec-icon" /> UV protection wax</li>
<li style="padding: 8px 0;"><LucideCheck class="spec-icon" /> Gel coat restoration</li>
</ul>
<button class="btn-primary-warm" style="width: 100%;" @click="handleQuote">Get Quote</button>
</div>
</div>
<div class="service-card" style="background: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); padding: 0; overflow: hidden; text-align: center; flex: 0 1 350px; min-width: 280px;">
<div style="width: 100%; height: 200px; overflow: hidden;">
<img src="/Anodes.jpg" alt="Zinc anode replacement service" style="width: 100%; height: 100%; object-fit: cover;">
</div>
<div style="padding: 30px;">
<h3 style="font-size: 24px; margin-bottom: 15px; color: #1e3a5f;">Anode Changes</h3>
<p style="color: #666; margin-bottom: 20px;">
Essential corrosion protection with regular zinc anode inspection and replacement.
</p>
<ul style="list-style: none; padding: 0; margin: 20px 0; text-align: left;">
<li style="padding: 8px 0;"><LucideCheck class="spec-icon" /> Prevents corrosion</li>
<li style="padding: 8px 0;"><LucideCheck class="spec-icon" /> Regular inspection</li>
<li style="padding: 8px 0;"><LucideCheck class="spec-icon" /> Marine-grade materials</li>
</ul>
<button class="btn-primary-warm" style="width: 100%;" @click="handleQuote">Get Quote</button>
</div>
</div>
<div class="service-card" style="background: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); padding: 0; overflow: hidden; text-align: center; flex: 0 1 350px; min-width: 280px;">
<div style="width: 100%; height: 200px; overflow: hidden;">
<img src="/Interior.jpg" alt="Professional interior detailing service" style="width: 100%; height: 100%; object-fit: cover;">
</div>
<div style="padding: 30px;">
<h3 style="font-size: 24px; margin-bottom: 15px; color: #1e3a5f;">Interior Detailing</h3>
<p style="color: #666; margin-bottom: 20px;">
Thorough interior cleaning and conditioning for a fresh, comfortable cabin.
</p>
<ul style="list-style: none; padding: 0; margin: 20px 0; text-align: left;">
<li style="padding: 8px 0;"><LucideCheck class="spec-icon" /> Upholstery cleaning</li>
<li style="padding: 8px 0;"><LucideCheck class="spec-icon" /> Mold &amp; mildew treatment</li>
<li style="padding: 8px 0;"><LucideCheck class="spec-icon" /> Surface conditioning</li>
</ul>
<button class="btn-primary-warm" style="width: 100%;" @click="handleQuote">Get Quote</button>
</div>
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
const handleQuote = () => {
window.location.href = 'tel:510-701-2535'
}
</script>
<style scoped>
/* Layout and styling provided by voyage-layout.css */
</style>

View File

@@ -0,0 +1,76 @@
<template>
<section class="experience-stories" id="testimonials">
<div class="story-container">
<h2 class="section-title center">What Our Customers Say</h2>
<div class="testimonial-highlight" style="max-width: 800px; margin: 40px auto; padding: 40px; background: white; border-radius: 15px; box-shadow: 0 8px 30px rgba(0,0,0,0.1); text-align: center;">
<div class="stars" style="margin-bottom: 20px;">
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 24px; height: 24px; display: inline-block;" />
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 24px; height: 24px; display: inline-block;" />
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 24px; height: 24px; display: inline-block;" />
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 24px; height: 24px; display: inline-block;" />
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 24px; height: 24px; display: inline-block;" />
</div>
<blockquote style="font-size: 22px; color: #1e3a5f; font-style: italic; line-height: 1.6; margin-bottom: 20px;">
"They do an amazing job and are always reliable! I never have to worry about my boat's condition."
</blockquote>
<cite style="font-weight: 600; color: #4b7cb8; font-size: 18px;">- John D.</cite>
</div>
<div class="stories-grid" style="display: flex; flex-wrap: wrap; gap: 30px; justify-content: center; max-width: 1200px; margin: 0 auto;">
<div class="story-card" style="flex: 0 1 350px; min-width: 280px;">
<div class="story-content" style="padding: 24px;">
<div class="stars" style="margin-bottom: 15px;">
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 18px; height: 18px;" />
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 18px; height: 18px;" />
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 18px; height: 18px;" />
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 18px; height: 18px;" />
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 18px; height: 18px;" />
</div>
<h3 style="font-size: 20px; font-weight: 600; margin-bottom: 10px;">Professional Service</h3>
<p style="color: #4a5568; margin-bottom: 12px;">"Harbor Smith keeps my boat in pristine condition. Their attention to detail is unmatched."</p>
<span style="color: #4b7cb8; font-weight: 600;">- Michael R.</span>
</div>
</div>
<div class="story-card" style="flex: 0 1 350px; min-width: 280px;">
<div class="story-content" style="padding: 24px;">
<div class="stars" style="margin-bottom: 15px;">
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 18px; height: 18px;" />
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 18px; height: 18px;" />
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 18px; height: 18px;" />
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 18px; height: 18px;" />
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 18px; height: 18px;" />
</div>
<h3 style="font-size: 20px; font-weight: 600; margin-bottom: 10px;">Convenient &amp; Reliable</h3>
<p style="color: #4a5568; margin-bottom: 12px;">"Mobile service that comes to my dock - it doesn't get better than that! Highly recommended."</p>
<span style="color: #4b7cb8; font-weight: 600;">- Sarah L.</span>
</div>
</div>
<div class="story-card" style="flex: 0 1 350px; min-width: 280px;">
<div class="story-content" style="padding: 24px;">
<div class="stars" style="margin-bottom: 15px;">
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 18px; height: 18px;" />
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 18px; height: 18px;" />
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 18px; height: 18px;" />
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 18px; height: 18px;" />
<LucideStar style="color: #fbbf24; fill: #fbbf24; width: 18px; height: 18px;" />
</div>
<h3 style="font-size: 20px; font-weight: 600; margin-bottom: 10px;">Excellent Value</h3>
<p style="color: #4a5568; margin-bottom: 12px;">"Fair pricing and exceptional quality. They've been maintaining my yacht for 5 years now."</p>
<span style="color: #4b7cb8; font-weight: 600;">- David K.</span>
</div>
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
// Lucide icons handled globally
</script>
<style scoped>
/* Card hover effects controlled via voyage-layout.css */
</style>

View File

@@ -0,0 +1,36 @@
<template>
<section class="services-section">
<div class="container">
<div class="service-stats">
<div class="stat-item">
<LucideShip class="stat-icon" />
<span class="stat-number">200+</span>
<span class="stat-label">Vessels Maintained</span>
</div>
<div class="stat-item">
<LucideAward class="stat-icon" />
<span class="stat-number">10+</span>
<span class="stat-label">Years Experience</span>
</div>
<div class="stat-item">
<LucideUsers class="stat-icon" />
<span class="stat-number">500+</span>
<span class="stat-label">Happy Clients</span>
</div>
<div class="stat-item">
<LucideShieldCheck class="stat-icon" />
<span class="stat-number">100%</span>
<span class="stat-label">Mobile Service</span>
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
// Icons registered globally via lucide plugin
</script>
<style scoped>
/* Styling sourced from voyage-layout.css */
</style>

View File

@@ -0,0 +1,64 @@
<template>
<section class="welcome-section">
<div class="container">
<div class="welcome-content">
<div class="welcome-text">
<h2 class="section-title warm">Why Choose Harbor Smith?</h2>
<p class="lead-text">
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.
</p>
<div class="feature-list">
<div class="feature-item">
<span class="feature-icon">
<LucideTruck />
</span>
<div>
<h4>Mobile Service</h4>
<p>We come to you - convenient service at your dock or marina</p>
</div>
</div>
<div class="feature-item">
<span class="feature-icon">
<LucideShieldCheck />
</span>
<div>
<h4>Certified Professionals</h4>
<p>Experienced technicians with marine industry certifications</p>
</div>
</div>
<div class="feature-item">
<span class="feature-icon">
<LucideCalendarCheck />
</span>
<div>
<h4>Reliable &amp; Consistent</h4>
<p>Regular maintenance schedules tailored to your needs</p>
</div>
</div>
</div>
</div>
<div class="welcome-image">
<img src="/leah_1.jpeg" alt="Harbor Smith team member" class="rounded-image">
<div class="image-badge">
<span>10+ Years</span>
<span>of Excellence</span>
</div>
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { useIntersectionAnimations } from '~/composables/useIntersectionAnimations'
onMounted(() => {
useIntersectionAnimations()
})
</script>
<style scoped>
/* Styling handled in voyage-layout.css */
</style>

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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()
})
}

View File

@@ -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

View File

@@ -0,0 +1,13 @@
<template>
<div class="min-h-screen flex flex-col">
<AppNavbar />
<main class="flex-grow">
<slot />
</main>
<AppFooter />
</div>
</template>
<script setup lang="ts">
// Layout components are auto-imported by Nuxt
</script>

99
apps/website/nginx.conf Normal file
View File

@@ -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;
}
}
}

159
apps/website/nuxt.config.ts Normal file
View File

@@ -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')
}
}
}
}
})

13939
apps/website/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

33
apps/website/package.json Normal file
View File

@@ -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"
}
}

View File

@@ -0,0 +1,55 @@
<template>
<div>
<!-- Hero Section with Video Background -->
<HeroSection />
<!-- Welcome Section -->
<WelcomeSection />
<!-- Services Section -->
<ServicesSection />
<!-- Trust Indicators Section -->
<TrustIndicators />
<!-- Testimonials Section -->
<TestimonialsSection />
<!-- Gallery Section -->
<GallerySection />
<!-- Booking Section -->
<BookingSection />
</div>
</template>
<script setup lang="ts">
// SEO meta tags aligned with static mockup
useHead({
title: 'Harbor Smith - Personalized Service Maintenance For Your Boat',
meta: [
{
name: 'description',
content: 'Keep your vessel pristine with San Francisco Bay\'s premier mobile boat maintenance service.'
},
{
property: 'og:title',
content: 'Harbor Smith - Personalized Service Maintenance For Your Boat'
},
{
property: 'og:description',
content: 'Keep your vessel pristine with San Francisco Bay\'s premier mobile boat maintenance service.'
},
{
property: 'og:image',
content: '/HARBOR-SMITH_navy.png'
},
{
name: 'twitter:card',
content: 'summary_large_image'
}
]
})
// Structured data temporarily disabled pending nuxt-seo-utils update
</script>

View File

@@ -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)
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

View File

@@ -0,0 +1,2 @@
User-Agent: *
Disallow:

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -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"
}
]
}

306
docker-compose.yml Normal file
View File

@@ -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

View File

@@ -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.*

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

159
nuxt.config.ts Normal file
View File

@@ -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')
}
}
}
}
})

75
package-lock.json generated Normal file
View File

@@ -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"
}
}
}
}

15
package.json Normal file
View File

@@ -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"
}
}

106
test-maintenance-pages.js Normal file
View File

@@ -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);

BIN
website-mockups/Anodes.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 MiB

View File

@@ -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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

View File

@@ -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 (
<div className="dropdown-container">
<button
className="dropdown-trigger"
onClick={() => setIsOpen(!isOpen)}
aria-expanded={isOpen}
aria-haspopup="listbox"
>
{icon && <span className="dropdown-icon">{icon}</span>}
<span className="dropdown-value">
{value || placeholder}
</span>
<ChevronDown
className={`dropdown-chevron ${isOpen ? 'rotate-180' : ''}`}
size={20}
/>
</button>
<AnimatePresence>
{isOpen && (
<motion.div
className="dropdown-menu"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }}
>
{options.map((option) => (
<motion.button
key={option.value}
className="dropdown-option"
onClick={() => {
onChange(option);
setIsOpen(false);
}}
whileHover={{ x: 4 }}
transition={{ type: "spring", stiffness: 300 }}
>
{option.icon && (
<span className="option-icon">{option.icon}</span>
)}
<span className="option-label">{option.label}</span>
{option.badge && (
<span className="option-badge">{option.badge}</span>
)}
</motion.button>
))}
</motion.div>
)}
</AnimatePresence>
</div>
);
};
```
##### 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
<DropdownMenu
label="Select Yacht"
options={[
{ value: 'azure', label: 'Azure Dream' },
{ value: 'seastar', label: 'Sea Star' },
{ value: 'windwhisper', label: 'Wind Whisper' }
]}
value={selectedYacht}
onChange={(option) => setSelectedYacht(option.value)}
/>
// Dropdown with Icons and Badges
<DropdownMenu
label="Choose Route"
icon={<MapPin />}
options={[
{
value: 'golden-gate',
label: 'Golden Gate Tour',
icon: <Bridge />,
badge: 'Popular'
},
{
value: 'alcatraz',
label: 'Alcatraz Circle',
icon: <Anchor />
},
{
value: 'sunset',
label: 'Sunset Cruise',
icon: <Sun />,
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
<link rel="stylesheet" href="css/voyage-layout.css">
<link rel="stylesheet" href="css/themes.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Playfair+Display:wght@400;700;900&display=swap" rel="stylesheet">
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
```
### Standard Page Structure
```html
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Meta tags -->
<!-- Font imports -->
<!-- CSS files -->
</head>
<body>
<!-- Navigation -->
<nav class="voyage-nav">...</nav>
<!-- Hero Section -->
<section class="hero-voyage">...</section>
<!-- Content Sections -->
<section class="section-name">
<div class="container">
<!-- Content -->
</div>
</section>
<!-- Footer -->
<footer class="voyage-footer">...</footer>
<!-- Scripts -->
<script>lucide.createIcons();</script>
</body>
</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.

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

View File

@@ -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.

BIN
website-mockups/Helm.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 MiB

View File

@@ -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*

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
website-mockups/QRCode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

BIN
website-mockups/Waxing.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

914
website-mockups/about.html Normal file
View File

@@ -0,0 +1,914 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>About HarborSmith - Our Story</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Playfair+Display:wght@400;700;900&display=swap" rel="stylesheet">
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
<link rel="stylesheet" href="css/voyage-layout.css">
<link rel="stylesheet" href="css/themes.css">
<style>
.about-hero {
position: relative;
min-height: 70vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
background: linear-gradient(135deg, #1e3a5f 0%, #2c5282 100%);
}
.about-hero::before {
content: '';
position: absolute;
inset: 0;
background-image: url('golden_gate.jpg');
background-size: cover;
background-position: center;
opacity: 0.4;
}
.about-hero-content {
position: relative;
z-index: 1;
text-align: center;
padding: 2rem;
max-width: 900px;
}
.about-hero h1 {
font-family: 'Playfair Display', serif;
font-size: 4rem;
font-weight: 900;
color: white;
margin-bottom: 1rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.about-hero p {
font-size: 1.5rem;
color: rgba(255, 255, 255, 0.95);
font-weight: 300;
line-height: 1.6;
}
.story-section {
padding: 5rem 0;
background: var(--bg-primary);
}
.story-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
align-items: center;
margin-bottom: 4rem;
}
.story-content.reverse {
direction: rtl;
}
.story-content.reverse .story-text {
direction: ltr;
}
.story-text h2 {
font-family: 'Playfair Display', serif;
font-size: 2.5rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 1rem;
}
.story-text h3 {
font-size: 1.125rem;
color: var(--accent-primary);
font-weight: 600;
margin-bottom: 1rem;
}
.story-text p {
color: var(--text-secondary);
line-height: 1.8;
margin-bottom: 1.5rem;
}
.story-image {
position: relative;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
}
.story-image img {
width: 100%;
height: auto;
display: block;
}
.story-image-caption {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(to top, rgba(0,0,0,0.7), transparent);
color: white;
padding: 1.5rem;
font-size: 0.875rem;
}
.values-section {
background: linear-gradient(135deg, rgba(30, 58, 95, 0.05) 0%, rgba(30, 58, 95, 0.02) 100%);
padding: 5rem 0;
}
.values-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
margin-top: 3rem;
}
.value-card {
background: var(--card-bg);
border-radius: 12px;
padding: 2rem;
text-align: center;
transition: all 0.3s ease;
}
.value-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
.value-icon {
width: 80px;
height: 80px;
margin: 0 auto 1.5rem;
background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-dark) 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.value-icon svg {
width: 40px;
height: 40px;
}
.value-title {
font-size: 1.5rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 1rem;
}
.value-description {
color: var(--text-secondary);
line-height: 1.6;
}
.team-section {
padding: 5rem 0;
}
.team-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
margin-top: 3rem;
}
.team-member {
background: var(--card-bg);
border-radius: 12px;
overflow: hidden;
transition: all 0.3s ease;
}
.team-member:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
.team-member-image {
height: 300px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 4rem;
font-weight: 700;
}
.team-member-info {
padding: 1.5rem;
}
.team-member-name {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 0.25rem;
}
.team-member-role {
color: var(--accent-primary);
font-weight: 500;
margin-bottom: 1rem;
}
.team-member-bio {
color: var(--text-secondary);
font-size: 0.875rem;
line-height: 1.6;
margin-bottom: 1rem;
}
.team-member-credentials {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.credential-badge {
padding: 0.25rem 0.75rem;
background: rgba(30, 58, 95, 0.1);
border-radius: 20px;
font-size: 0.75rem;
color: var(--text-secondary);
}
.milestones-section {
background: var(--card-bg);
padding: 5rem 0;
}
.timeline {
position: relative;
max-width: 1000px;
margin: 3rem auto;
}
.timeline::before {
content: '';
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 100%;
background: var(--accent-primary);
opacity: 0.3;
}
.timeline-item {
display: flex;
align-items: center;
margin-bottom: 3rem;
position: relative;
}
.timeline-item:nth-child(even) {
flex-direction: row-reverse;
}
.timeline-content {
flex: 1;
padding: 1.5rem;
background: var(--bg-primary);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
}
.timeline-item:nth-child(odd) .timeline-content {
margin-right: 2rem;
text-align: right;
}
.timeline-item:nth-child(even) .timeline-content {
margin-left: 2rem;
}
.timeline-dot {
width: 20px;
height: 20px;
background: var(--accent-primary);
border-radius: 50%;
position: absolute;
left: 50%;
transform: translateX(-50%);
border: 4px solid var(--card-bg);
}
.timeline-year {
font-size: 1.5rem;
font-weight: 700;
color: var(--accent-primary);
margin-bottom: 0.5rem;
}
.timeline-title {
font-size: 1.125rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 0.5rem;
}
.timeline-description {
color: var(--text-secondary);
font-size: 0.875rem;
line-height: 1.6;
}
.certifications-section {
padding: 5rem 0;
text-align: center;
}
.cert-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 2rem;
margin-top: 3rem;
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
.cert-item {
background: var(--card-bg);
border-radius: 12px;
padding: 2rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 150px;
transition: all 0.3s ease;
}
.cert-item:hover {
transform: scale(1.05);
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
.cert-logo {
font-size: 1.5rem;
font-weight: 700;
color: var(--accent-primary);
margin-bottom: 0.5rem;
}
.cert-name {
font-size: 0.875rem;
color: var(--text-secondary);
}
.location-section {
background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-dark) 100%);
padding: 5rem 0;
color: white;
}
.location-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
align-items: center;
}
.location-info h2 {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 1rem;
}
.location-info p {
font-size: 1.125rem;
line-height: 1.8;
margin-bottom: 2rem;
opacity: 0.95;
}
.location-details {
display: grid;
gap: 1rem;
}
.location-item {
display: flex;
align-items: center;
gap: 1rem;
}
.location-item svg {
width: 24px;
height: 24px;
opacity: 0.9;
}
.map-container {
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
height: 400px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
opacity: 0.9;
}
@media (max-width: 768px) {
.about-hero h1 {
font-size: 2.5rem;
}
.story-content,
.story-content.reverse {
grid-template-columns: 1fr;
direction: ltr;
}
.timeline::before {
left: 20px;
}
.timeline-item,
.timeline-item:nth-child(even) {
flex-direction: row;
}
.timeline-item:nth-child(odd) .timeline-content,
.timeline-item:nth-child(even) .timeline-content {
margin-left: 3rem;
margin-right: 0;
text-align: left;
}
.timeline-dot {
left: 20px;
}
.location-content {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<!-- Navigation -->
<nav class="voyage-nav visible" id="voyageNav">
<div class="nav-container">
<div class="nav-brand">
<img src="logo.jpg" alt="HarborSmith" class="nav-logo">
<span>HarborSmith</span>
</div>
<div class="nav-links">
<a href="index.html" class="nav-link">Home</a>
<a href="charter.html" class="nav-link">Charters</a>
<a href="maintenance.html" class="nav-link">Maintenance</a>
<a href="about.html" class="nav-link active">About</a>
<a href="contact.html" class="nav-link">Contact</a>
</div>
</div>
</nav>
<!-- Hero Section -->
<section class="about-hero">
<div class="about-hero-content">
<h1>Our Story</h1>
<p>Three generations of maritime excellence, serving the San Francisco Bay since 2009</p>
</div>
</section>
<!-- Story Section -->
<section class="story-section">
<div class="container">
<div class="story-content">
<div class="story-text">
<h3>Est. 2009</h3>
<h2>Born from Passion</h2>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
</div>
<div class="story-image">
<img src="golden_gate.jpg" alt="Our journey">
<div class="story-image-caption">
Our flagship yacht passing under the Golden Gate Bridge
</div>
</div>
</div>
<div class="story-content reverse">
<div class="story-text">
<h3>The HarborSmith Difference</h3>
<h2>More Than a Service</h2>
<p>
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.
</p>
<p>
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.
</p>
<p>
Whether you're celebrating a special occasion, maintaining your prized vessel, or simply escaping
the everyday, we're here to make it extraordinary.
</p>
</div>
<div class="story-image">
<img src="yacht_party_couple_holding_hands.jpg" alt="Happy customers">
<div class="story-image-caption">
Creating unforgettable moments for our clients
</div>
</div>
</div>
</div>
</section>
<!-- Values Section -->
<section class="values-section">
<div class="container">
<div class="section-header">
<h2 class="section-title">Our Core Values</h2>
<p class="section-subtitle">The principles that guide everything we do</p>
</div>
<div class="values-grid">
<div class="value-card">
<div class="value-icon">
<i data-lucide="shield"></i>
</div>
<h3 class="value-title">Safety First</h3>
<p class="value-description">
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.
</p>
</div>
<div class="value-card">
<div class="value-icon">
<i data-lucide="star"></i>
</div>
<h3 class="value-title">Excellence</h3>
<p class="value-description">
We don't just meet expectations we exceed them. From our premium fleet to our expert
technicians, we deliver nothing but the best.
</p>
</div>
<div class="value-card">
<div class="value-icon">
<i data-lucide="heart"></i>
</div>
<h3 class="value-title">Passion</h3>
<p class="value-description">
We love what we do, and it shows. Our enthusiasm for maritime life is contagious, making
every interaction a pleasure.
</p>
</div>
<div class="value-card">
<div class="value-icon">
<i data-lucide="users"></i>
</div>
<h3 class="value-title">Family</h3>
<p class="value-description">
As a family business, we treat our customers, employees, and community like family. Your
trust is our most valuable asset.
</p>
</div>
</div>
</div>
</section>
<!-- Team Section -->
<section class="team-section">
<div class="container">
<div class="section-header">
<h2 class="section-title">Meet Our Crew</h2>
<p class="section-subtitle">The experienced professionals behind your perfect day on the water</p>
</div>
<div class="team-grid">
<div class="team-member">
<div class="team-member-image">JH</div>
<div class="team-member-info">
<h3 class="team-member-name">Captain James Harbor</h3>
<p class="team-member-role">Founder & Head Captain</p>
<p class="team-member-bio">
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.
</p>
<div class="team-member-credentials">
<span class="credential-badge">USCG Licensed</span>
<span class="credential-badge">30+ Years Experience</span>
<span class="credential-badge">CPR Certified</span>
</div>
</div>
</div>
<div class="team-member">
<div class="team-member-image">SS</div>
<div class="team-member-info">
<h3 class="team-member-name">Sarah Smith</h3>
<p class="team-member-role">Co-Founder & Service Director</p>
<p class="team-member-bio">
A master technician with certifications from all major marine manufacturers, Sarah ensures
every yacht in our care receives the best possible service.
</p>
<div class="team-member-credentials">
<span class="credential-badge">ABYC Certified</span>
<span class="credential-badge">Mercury Master Tech</span>
<span class="credential-badge">Volvo Specialist</span>
</div>
</div>
</div>
<div class="team-member">
<div class="team-member-image">MH</div>
<div class="team-member-info">
<h3 class="team-member-name">Michael Harbor</h3>
<p class="team-member-role">Charter Captain</p>
<p class="team-member-bio">
Following in his father's footsteps, Michael brings youthful energy and extensive local
knowledge to every charter, ensuring guests have an unforgettable experience.
</p>
<div class="team-member-credentials">
<span class="credential-badge">USCG Licensed</span>
<span class="credential-badge">10+ Years Experience</span>
<span class="credential-badge">Dive Master</span>
</div>
</div>
</div>
<div class="team-member">
<div class="team-member-image">RT</div>
<div class="team-member-info">
<h3 class="team-member-name">Roberto Torres</h3>
<p class="team-member-role">Lead Technician</p>
<p class="team-member-bio">
Roberto's expertise in marine electronics and engine systems makes him invaluable. His
attention to detail ensures every yacht runs perfectly.
</p>
<div class="team-member-credentials">
<span class="credential-badge">NMEA Certified</span>
<span class="credential-badge">Yamaha Specialist</span>
<span class="credential-badge">15+ Years Experience</span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Milestones Section -->
<section class="milestones-section">
<div class="container">
<div class="section-header">
<h2 class="section-title">Our Journey</h2>
<p class="section-subtitle">Milestones that shaped who we are today</p>
</div>
<div class="timeline">
<div class="timeline-item">
<div class="timeline-content">
<div class="timeline-year">2009</div>
<div class="timeline-title">HarborSmith Founded</div>
<div class="timeline-description">
James and Sarah start HarborSmith with one boat and a dream
</div>
</div>
<div class="timeline-dot"></div>
</div>
<div class="timeline-item">
<div class="timeline-content">
<div class="timeline-year">2012</div>
<div class="timeline-title">Fleet Expansion</div>
<div class="timeline-description">
Added two more yachts and opened our maintenance facility
</div>
</div>
<div class="timeline-dot"></div>
</div>
<div class="timeline-item">
<div class="timeline-content">
<div class="timeline-year">2015</div>
<div class="timeline-title">100th Charter</div>
<div class="timeline-description">
Celebrated our 100th successful charter with a community event
</div>
</div>
<div class="timeline-dot"></div>
</div>
<div class="timeline-item">
<div class="timeline-content">
<div class="timeline-year">2018</div>
<div class="timeline-title">Award Recognition</div>
<div class="timeline-description">
Named "Best Yacht Services" by Bay Area Boating Magazine
</div>
</div>
<div class="timeline-dot"></div>
</div>
<div class="timeline-item">
<div class="timeline-content">
<div class="timeline-year">2020</div>
<div class="timeline-title">Community Support</div>
<div class="timeline-description">
Provided free maintenance to first responders during pandemic
</div>
</div>
<div class="timeline-dot"></div>
</div>
<div class="timeline-item">
<div class="timeline-content">
<div class="timeline-year">2024</div>
<div class="timeline-title">15 Years Strong</div>
<div class="timeline-description">
Celebrating 15 years of service with our biggest fleet yet
</div>
</div>
<div class="timeline-dot"></div>
</div>
</div>
</div>
</section>
<!-- Certifications Section -->
<section class="certifications-section">
<div class="container">
<div class="section-header">
<h2 class="section-title">Certified Excellence</h2>
<p class="section-subtitle">Recognized by leading maritime organizations</p>
</div>
<div class="cert-grid">
<div class="cert-item">
<div class="cert-logo">USCG</div>
<div class="cert-name">US Coast Guard</div>
</div>
<div class="cert-item">
<div class="cert-logo">ABYC</div>
<div class="cert-name">American Boat & Yacht Council</div>
</div>
<div class="cert-item">
<div class="cert-logo">NMEA</div>
<div class="cert-name">National Marine Electronics</div>
</div>
<div class="cert-item">
<div class="cert-logo">BBB</div>
<div class="cert-name">Better Business Bureau</div>
</div>
</div>
</div>
</section>
<!-- Location Section -->
<section class="location-section">
<div class="container">
<div class="location-content">
<div class="location-info">
<h2>Find Us at Pier 39</h2>
<p>
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.
</p>
<div class="location-details">
<div class="location-item">
<i data-lucide="map-pin"></i>
<span>Pier 39, Dock J, San Francisco, CA 94133</span>
</div>
<div class="location-item">
<i data-lucide="clock"></i>
<span>Open 7 days a week, 8:00 AM - 8:00 PM</span>
</div>
<div class="location-item">
<i data-lucide="car"></i>
<span>Free parking for charter guests</span>
</div>
<div class="location-item">
<i data-lucide="train"></i>
<span>BART and Muni accessible</span>
</div>
</div>
</div>
<div class="map-container">
<i data-lucide="map" style="width: 60px; height: 60px;"></i>
</div>
</div>
</div>
</section>
<!-- CTA Section -->
<section class="booking-cta">
<div class="booking-container">
<div class="booking-content">
<h2 class="booking-title">Ready to Experience the HarborSmith Difference?</h2>
<p class="booking-subtitle">
Join our family of satisfied customers and discover why we're the Bay Area's premier yacht service
</p>
<div class="booking-options">
<div class="booking-card">
<span class="booking-icon">
<i data-lucide="sailboat"></i>
</span>
<h3>Book a Charter</h3>
<p>Start your adventure today</p>
<button class="btn-booking" onclick="window.location.href='charter.html'">
Explore Charters
</button>
</div>
<div class="booking-card featured">
<span class="booking-icon">
<i data-lucide="phone"></i>
</span>
<h3>Call Us</h3>
<p>Speak with our friendly team</p>
<button class="btn-booking primary" onclick="window.location.href='tel:4155550123'">
(415) 555-0123
</button>
</div>
<div class="booking-card">
<span class="booking-icon">
<i data-lucide="wrench"></i>
</span>
<h3>Schedule Service</h3>
<p>Keep your yacht pristine</p>
<button class="btn-booking" onclick="window.location.href='maintenance.html'">
View Services
</button>
</div>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="voyage-footer">
<div class="footer-container">
<div class="footer-content">
<div class="footer-brand">
<img src="logo.jpg" alt="HarborSmith" class="footer-logo">
<h3>HarborSmith</h3>
<p>Your trusted partner for Bay Area yacht services since 2009</p>
</div>
<div class="footer-links">
<h4>Quick Links</h4>
<a href="index.html">Home</a>
<a href="charter.html">Charters</a>
<a href="maintenance.html">Maintenance</a>
<a href="about.html">About</a>
<a href="contact.html">Contact</a>
</div>
<div class="footer-contact">
<h4>Get in Touch</h4>
<p><i data-lucide="map-pin" class="footer-icon"></i> Pier 39, San Francisco, CA</p>
<p><i data-lucide="phone" class="footer-icon"></i> (415) 555-0123</p>
<p><i data-lucide="mail" class="footer-icon"></i> hello@harborsmith.com</p>
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2024 HarborSmith Yacht Services. All rights reserved.</p>
</div>
</div>
</footer>
<script>
// Initialize Lucide icons
lucide.createIcons();
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More