Initial import of HarborSmith website
Some checks failed
build-website / build (push) Failing after 1m2s
49
.dockerignore
Normal 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
|
||||
31
.gitea/workflows/build.yml
Normal 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
@@ -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
@@ -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
1397
LANDING_PAGE_CONVERSION_PLAN.md
Normal file
402
NAUTICAL_DESIGN_SYSTEM.md
Normal 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
@@ -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
@@ -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
@@ -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.
|
||||
371
apps/website/assets/css/main.css
Normal 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;
|
||||
}
|
||||
}
|
||||
174
apps/website/assets/css/themes.css
Normal 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);
|
||||
}
|
||||
1659
apps/website/assets/css/voyage-layout.css
Normal file
38
apps/website/components/AppFooter.vue
Normal 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>© 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>
|
||||
85
apps/website/components/AppNavbar.vue
Normal 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>
|
||||
59
apps/website/components/BookingSection.vue
Normal 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>
|
||||
56
apps/website/components/GallerySection.vue
Normal 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>
|
||||
148
apps/website/components/HeroSection.vue
Normal 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>
|
||||
94
apps/website/components/ServicesSection.vue
Normal 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 & 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 & 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>
|
||||
76
apps/website/components/TestimonialsSection.vue
Normal 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 & 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>
|
||||
36
apps/website/components/TrustIndicators.vue
Normal 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>
|
||||
64
apps/website/components/WelcomeSection.vue
Normal 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 & 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>
|
||||
96
apps/website/composables/useIntersectionAnimations.js
Normal 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
|
||||
}
|
||||
}
|
||||
47
apps/website/composables/useParallax.js
Normal 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
|
||||
}
|
||||
}
|
||||
79
apps/website/composables/useRipple.js
Normal 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()
|
||||
})
|
||||
}
|
||||
22
apps/website/docker-compose.yml
Normal 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
|
||||
13
apps/website/layouts/default.vue
Normal 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
@@ -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
@@ -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
33
apps/website/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
55
apps/website/pages/index.vue
Normal 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>
|
||||
47
apps/website/plugins/lucide.client.js
Normal 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)
|
||||
})
|
||||
|
||||
BIN
apps/website/public/Anodes.jpg
Normal file
|
After Width: | Height: | Size: 6.5 MiB |
BIN
apps/website/public/Calendar.jpg
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
apps/website/public/ExtCleaning.jpg
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
apps/website/public/Foredeck.jpg
Normal file
|
After Width: | Height: | Size: 5.1 MiB |
BIN
apps/website/public/HARBOR-SMITH-white.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
apps/website/public/HARBOR-SMITH_navy.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
apps/website/public/Helm.jpg
Normal file
|
After Width: | Height: | Size: 3.7 MiB |
BIN
apps/website/public/Hull Clean Pricing.jpg
Normal file
|
After Width: | Height: | Size: 533 KiB |
BIN
apps/website/public/Interior.jpg
Normal file
|
After Width: | Height: | Size: 4.5 MiB |
BIN
apps/website/public/Licensed.jpg
Normal file
|
After Width: | Height: | Size: 7.3 MiB |
BIN
apps/website/public/QRCode-White-sm.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
apps/website/public/QRCode-White.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
apps/website/public/QRCode.png
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
apps/website/public/SpecialRequest.jpg
Normal file
|
After Width: | Height: | Size: 4.2 MiB |
BIN
apps/website/public/Washdown.jpg
Normal file
|
After Width: | Height: | Size: 6.1 MiB |
BIN
apps/website/public/Washdown2.jpg
Normal file
|
After Width: | Height: | Size: 4.4 MiB |
BIN
apps/website/public/Waxing.jpg
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
2
apps/website/public/_robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-Agent: *
|
||||
Disallow:
|
||||
BIN
apps/website/public/diver_cleaning.jpg
Normal file
|
After Width: | Height: | Size: 4.1 MiB |
BIN
apps/website/public/diver_cleaning_2.jpg
Normal file
|
After Width: | Height: | Size: 8.1 MiB |
BIN
apps/website/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
apps/website/public/golden_gate.jpg
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
apps/website/public/iStock-2189089654.jpg
Normal file
|
After Width: | Height: | Size: 5.5 MiB |
BIN
apps/website/public/iStock-504868014.jpg
Normal file
|
After Width: | Height: | Size: 6.4 MiB |
BIN
apps/website/public/iStock-923244752.jpg
Normal file
|
After Width: | Height: | Size: 9.1 MiB |
BIN
apps/website/public/iStock-923244752b.jpg
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
apps/website/public/kid_1.jpeg
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
apps/website/public/leah_1.jpeg
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
apps/website/public/sausalito-boat-show-2024.jpg
Normal file
|
After Width: | Height: | Size: 427 KiB |
BIN
apps/website/public/sf_bay_exposure.jpg
Normal file
|
After Width: | Height: | Size: 99 KiB |
18
apps/website/tsconfig.json
Normal 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
@@ -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
|
||||
829
docs/GITEA_DOCKER_WORKFLOW.md
Normal 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.*
|
||||
1415
docs/HARBORSMITH_IMPLEMENTATION_PLAN.md
Normal file
BIN
maintenance-documents-screenshot.png
Normal file
|
After Width: | Height: | Size: 168 KiB |
BIN
maintenance-invoices-screenshot.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
maintenance-reports-screenshot.png
Normal file
|
After Width: | Height: | Size: 432 KiB |
BIN
maintenance-schedule-screenshot.png
Normal file
|
After Width: | Height: | Size: 167 KiB |
159
nuxt.config.ts
Normal 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
@@ -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
@@ -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
@@ -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
|
After Width: | Height: | Size: 6.5 MiB |
327
website-mockups/CLIENT-PORTAL-IMPLEMENTATION-GUIDE.md
Normal 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.
|
||||
BIN
website-mockups/Calendar.jpg
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
814
website-mockups/DESIGN-SYSTEM.md
Normal 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.
|
||||
2
website-mockups/Design-Notes.md
Normal 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
|
||||
BIN
website-mockups/ExtCleaning.jpg
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
website-mockups/Foredeck.jpg
Normal file
|
After Width: | Height: | Size: 5.1 MiB |
BIN
website-mockups/HARBOR-SMITH-white.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
website-mockups/HARBOR-SMITH_navy.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
website-mockups/HS-Homepage.pdf
Normal file
272
website-mockups/HarborSmith-Platform-Definition.md
Normal 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
|
After Width: | Height: | Size: 3.7 MiB |
BIN
website-mockups/Hull Clean Pricing.jpg
Normal file
|
After Width: | Height: | Size: 533 KiB |
BIN
website-mockups/Interior.jpg
Normal file
|
After Width: | Height: | Size: 4.5 MiB |
BIN
website-mockups/Licensed.jpg
Normal file
|
After Width: | Height: | Size: 7.3 MiB |
212
website-mockups/PROJECT_STATUS.md
Normal 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*
|
||||
BIN
website-mockups/QRCode-White-sm.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
website-mockups/QRCode-White.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
website-mockups/QRCode.png
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
website-mockups/SpecialRequest.jpg
Normal file
|
After Width: | Height: | Size: 4.2 MiB |
BIN
website-mockups/Washdown.jpg
Normal file
|
After Width: | Height: | Size: 6.1 MiB |
BIN
website-mockups/Washdown2.jpg
Normal file
|
After Width: | Height: | Size: 4.4 MiB |
BIN
website-mockups/Waxing.jpg
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
914
website-mockups/about.html
Normal 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>© 2024 HarborSmith Yacht Services. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// Initialize Lucide icons
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||