feat: Migrate to Next.js with complete mobile-optimized website
Some checks failed
build-website / build (push) Failing after 7s
- Replaced Vue/Nuxt with Next.js 15 for better performance and simpler architecture - Implemented all website sections with responsive design: - Hero section with video background and mobile-optimized spacing - About section with feature highlights - Services showcase with 3 service cards - Contact section with CTAs and trust badges - Footer with branding - Added Lucide React icons throughout - Mobile optimizations: - Responsive text and button sizing - Touch-friendly CTAs - Proper spacing adjustments for mobile/desktop - Scroll indicator with bouncing chevron - Archived Vue/Nuxt version in vue-archive folder - Moved all assets to Next.js public folder 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
74
README.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# HarborSmith - Professional Boat Maintenance Services
|
||||||
|
|
||||||
|
Harbor Smith provides personalized service maintenance for boats in the San Francisco Bay Area.
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Framework**: Next.js 15
|
||||||
|
- **Styling**: Tailwind CSS v4
|
||||||
|
- **Icons**: Lucide React
|
||||||
|
- **Language**: TypeScript
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
✅ **Complete Website Sections**
|
||||||
|
- Hero section with video background
|
||||||
|
- About section (Why Choose Harbor Smith)
|
||||||
|
- Services showcase (Hull Cleaning, Exterior Cleaning, Interior Detailing)
|
||||||
|
- Contact section with CTAs
|
||||||
|
- Footer with branding
|
||||||
|
|
||||||
|
✅ **Mobile Optimized**
|
||||||
|
- Responsive design for all screen sizes
|
||||||
|
- Touch-friendly CTAs
|
||||||
|
- Optimized spacing and typography
|
||||||
|
- Mobile-specific UI adjustments
|
||||||
|
|
||||||
|
✅ **iOS Safari Compatible**
|
||||||
|
- Video background works on all devices
|
||||||
|
- Safe area handling for modern iPhones
|
||||||
|
- Smooth scrolling animations
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Run development server
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Build for production
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Start production server
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
/
|
||||||
|
├── app/ # Next.js app directory
|
||||||
|
├── components/ # React components
|
||||||
|
├── public/ # Static assets (images, videos)
|
||||||
|
├── styles/ # Global styles
|
||||||
|
└── vue-archive/ # Archived Vue/Nuxt version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
This project is ready for deployment to Vercel, Netlify, or any Node.js hosting platform.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
- Phone: (510) 701-2535
|
||||||
|
- Email: info@harborsmith.com
|
||||||
|
- Service Area: San Francisco Bay Area
|
||||||
19
app/globals.css
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* iOS Safari Safe Area Support */
|
||||||
|
html {
|
||||||
|
/* Allow content to extend into safe areas */
|
||||||
|
min-height: 100vh;
|
||||||
|
min-height: -webkit-fill-available;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
min-height: -webkit-fill-available;
|
||||||
|
/* Transparent background to allow video to show through */
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
42
app/layout.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import type { Metadata, Viewport } from 'next'
|
||||||
|
import { Inter, Playfair_Display } from 'next/font/google'
|
||||||
|
import './globals.css'
|
||||||
|
|
||||||
|
const inter = Inter({
|
||||||
|
subsets: ['latin'],
|
||||||
|
variable: '--font-sans',
|
||||||
|
})
|
||||||
|
|
||||||
|
const playfair = Playfair_Display({
|
||||||
|
subsets: ['latin'],
|
||||||
|
variable: '--font-serif',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const viewport: Viewport = {
|
||||||
|
width: 'device-width',
|
||||||
|
initialScale: 1,
|
||||||
|
viewportFit: 'cover', // Critical for iOS Safari safe areas
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Harbor Smith - Personalized Service Maintenance For Your Boat',
|
||||||
|
description: 'Reliable Care Above and Below the Waterline. Servicing the Bay Area and Beyond!',
|
||||||
|
appleWebApp: {
|
||||||
|
capable: true,
|
||||||
|
statusBarStyle: 'black-translucent',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<html lang="en" className={`${inter.variable} ${playfair.variable}`}>
|
||||||
|
<body className="font-sans">
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
21
app/page.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import HeroSection from '@/components/HeroSection'
|
||||||
|
import AboutSection from '@/components/AboutSection'
|
||||||
|
import ServicesSection from '@/components/ServicesSection'
|
||||||
|
import TrustIndicators from '@/components/TrustIndicators'
|
||||||
|
import ContactSection from '@/components/ContactSection'
|
||||||
|
import Footer from '@/components/Footer'
|
||||||
|
|
||||||
|
export default function HomePage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main>
|
||||||
|
<HeroSection />
|
||||||
|
<AboutSection />
|
||||||
|
<ServicesSection />
|
||||||
|
<TrustIndicators />
|
||||||
|
<ContactSection />
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
76
components/AboutSection.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Wrench, ShieldCheck, CalendarCheck } from 'lucide-react'
|
||||||
|
|
||||||
|
export default function AboutSection() {
|
||||||
|
return (
|
||||||
|
<section className="py-16 md:py-24 bg-white">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12 items-center max-w-6xl mx-auto">
|
||||||
|
{/* Text Content */}
|
||||||
|
<div className="order-2 lg:order-1">
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold text-harbor-blue mb-6">
|
||||||
|
Why Choose Harbor Smith?
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-gray-600 mb-8">
|
||||||
|
We're the San Francisco Bay Area's premier mobile boat maintenance service.
|
||||||
|
Our professional team provides expert care, ensuring your vessel stays in pristine condition year-round.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Features List */}
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<div className="w-12 h-12 bg-harbor-gold/10 rounded-lg flex items-center justify-center">
|
||||||
|
<Wrench className="w-6 h-6 text-harbor-gold" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-lg font-semibold text-harbor-blue mb-1">Tailored Service</h4>
|
||||||
|
<p className="text-gray-600">We provide highly customized service that fits your needs.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<div className="w-12 h-12 bg-harbor-gold/10 rounded-lg flex items-center justify-center">
|
||||||
|
<ShieldCheck className="w-6 h-6 text-harbor-gold" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-lg font-semibold text-harbor-blue mb-1">Certified Professionals</h4>
|
||||||
|
<p className="text-gray-600">Experienced technicians with marine industry certifications</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<div className="w-12 h-12 bg-harbor-gold/10 rounded-lg flex items-center justify-center">
|
||||||
|
<CalendarCheck className="w-6 h-6 text-harbor-gold" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-lg font-semibold text-harbor-blue mb-1">Reliable & Consistent</h4>
|
||||||
|
<p className="text-gray-600">Regular maintenance schedules tailored to your needs</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Image */}
|
||||||
|
<div className="order-1 lg:order-2 relative">
|
||||||
|
<img
|
||||||
|
src="/leah_1.jpeg"
|
||||||
|
alt="Harbor Smith team member"
|
||||||
|
className="w-full rounded-xl shadow-xl"
|
||||||
|
/>
|
||||||
|
<div className="absolute bottom-4 right-4 bg-harbor-blue text-white px-6 py-3 rounded-lg">
|
||||||
|
<span className="block text-2xl font-bold">10+ Years</span>
|
||||||
|
<span className="text-sm">of Excellence</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
56
components/ContactSection.tsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Phone, Mail, MapPin, Clock } from 'lucide-react'
|
||||||
|
|
||||||
|
export default function ContactSection() {
|
||||||
|
return (
|
||||||
|
<section id="contact" className="py-16 md:py-24 bg-harbor-blue text-white">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
<div className="max-w-4xl mx-auto text-center">
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold mb-4">Ready to Schedule Service?</h2>
|
||||||
|
<p className="text-lg md:text-xl mb-12 opacity-90">
|
||||||
|
Contact us today for a free consultation and quote
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
|
{/* Contact Info */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-center md:justify-start gap-3">
|
||||||
|
<Phone className="w-5 h-5 text-harbor-gold" />
|
||||||
|
<a href="tel:510-701-2535" className="hover:text-harbor-gold transition-colors">
|
||||||
|
(510) 701-2535
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center md:justify-start gap-3">
|
||||||
|
<Mail className="w-5 h-5 text-harbor-gold" />
|
||||||
|
<a href="mailto:info@harborsmith.com" className="hover:text-harbor-gold transition-colors">
|
||||||
|
info@harborsmith.com
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center md:justify-start gap-3">
|
||||||
|
<MapPin className="w-5 h-5 text-harbor-gold" />
|
||||||
|
<span>San Francisco Bay Area</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center md:justify-start gap-3">
|
||||||
|
<Clock className="w-5 h-5 text-harbor-gold" />
|
||||||
|
<span>Mon-Sat: 8AM-6PM</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTA */}
|
||||||
|
<div className="flex flex-col justify-center">
|
||||||
|
<h3 className="text-2xl font-bold mb-4">Get Your Free Quote</h3>
|
||||||
|
<p className="mb-6 opacity-90">Professional service at competitive prices</p>
|
||||||
|
<a
|
||||||
|
href="tel:510-701-2535"
|
||||||
|
className="inline-block bg-harbor-gold text-white font-semibold py-4 px-8 rounded-lg hover:bg-harbor-amber transition-colors mx-auto md:mx-0"
|
||||||
|
>
|
||||||
|
Call Now: (510) 701-2535
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
25
components/Footer.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<footer className="bg-gray-900 text-white py-8">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
<div className="flex flex-col md:flex-row justify-between items-center">
|
||||||
|
<div className="mb-4 md:mb-0">
|
||||||
|
<img
|
||||||
|
src="/HARBOR-SMITH-white.png"
|
||||||
|
alt="Harbor Smith"
|
||||||
|
className="h-12 md:h-16 w-auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="text-center md:text-right">
|
||||||
|
<p className="text-sm opacity-80">
|
||||||
|
© {new Date().getFullYear()} Harbor Smith Marine Services. All rights reserved.
|
||||||
|
</p>
|
||||||
|
<p className="text-xs opacity-60 mt-1">
|
||||||
|
Professional Boat Maintenance | San Francisco Bay Area
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}
|
||||||
117
components/HeroSection.tsx
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Phone, Wrench, ChevronDown } from 'lucide-react'
|
||||||
|
|
||||||
|
export default function HeroSection() {
|
||||||
|
const [videoLoaded, setVideoLoaded] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Detect iOS and set body position for safe area support
|
||||||
|
const isIOS = /iPhone|iPad|iPod/.test(navigator.userAgent)
|
||||||
|
if (isIOS) {
|
||||||
|
document.body.style.position = 'fixed'
|
||||||
|
document.body.style.width = '100%'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle video loading with fallback
|
||||||
|
const video = document.getElementById('hero-video') as HTMLVideoElement
|
||||||
|
if (video) {
|
||||||
|
// Check if video can play
|
||||||
|
if (video.readyState >= 3) {
|
||||||
|
setVideoLoaded(true)
|
||||||
|
} else {
|
||||||
|
video.addEventListener('loadeddata', () => {
|
||||||
|
setVideoLoaded(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback timeout to show content even if video doesn't load
|
||||||
|
setTimeout(() => {
|
||||||
|
setVideoLoaded(true)
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
// iOS autoplay fallback
|
||||||
|
const tryPlay = () => {
|
||||||
|
if (video.paused) {
|
||||||
|
video.play().catch(() => {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('touchstart', tryPlay, { once: true })
|
||||||
|
} else {
|
||||||
|
// If video element doesn't exist, show content anyway
|
||||||
|
setVideoLoaded(true)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="relative h-screen w-full overflow-hidden">
|
||||||
|
{/* Video Background */}
|
||||||
|
<div className="absolute inset-0 w-full h-full">
|
||||||
|
<video
|
||||||
|
id="hero-video"
|
||||||
|
autoPlay
|
||||||
|
muted
|
||||||
|
loop
|
||||||
|
playsInline
|
||||||
|
className="absolute inset-0 h-full w-full object-cover"
|
||||||
|
>
|
||||||
|
<source
|
||||||
|
src="https://videos.pexels.com/video-files/3571264/3571264-uhd_2560_1440_30fps.mp4"
|
||||||
|
type="video/mp4"
|
||||||
|
/>
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Overlay - Separate from video container */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-harbor-blue/30 via-harbor-blue/50 to-harbor-blue/70 z-[1]" />
|
||||||
|
|
||||||
|
{/* Hero Content */}
|
||||||
|
<div className={`relative z-10 flex h-full flex-col items-center justify-start pt-24 md:justify-center md:pt-0 px-4 text-white transition-opacity duration-1000 ${videoLoaded ? 'opacity-100' : 'opacity-0'}`}>
|
||||||
|
<div className="max-w-3xl text-center">
|
||||||
|
{/* Logo - Responsive sizing */}
|
||||||
|
<img
|
||||||
|
src="/HARBOR-SMITH-white.png"
|
||||||
|
alt="Harbor Smith"
|
||||||
|
className="mx-auto mb-3 md:mb-2 h-auto w-64 sm:w-80 md:w-96 lg:w-[400px]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Tagline - Responsive text sizing - much tighter on desktop */}
|
||||||
|
<p className="mb-1 md:mb-1 text-xl sm:text-2xl md:text-3xl font-medium">
|
||||||
|
Personalized Service Maintenance for Your Boat
|
||||||
|
</p>
|
||||||
|
<p className="mb-6 md:mb-3 text-base sm:text-lg md:text-xl font-light px-4">
|
||||||
|
Reliable Care Above and Below the Waterline. Servicing the Bay Area and Beyond!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* CTAs - Responsive button sizing - more space on mobile */}
|
||||||
|
<div className="flex flex-col gap-3 sm:gap-4 sm:flex-row sm:justify-center px-4">
|
||||||
|
<a
|
||||||
|
href="tel:510-701-2535"
|
||||||
|
className="flex items-center justify-center gap-2 rounded-lg bg-harbor-gold px-6 sm:px-8 py-3 sm:py-4 text-sm sm:text-base font-semibold text-white transition hover:bg-harbor-amber"
|
||||||
|
>
|
||||||
|
<Phone className="w-4 h-4 sm:w-5 sm:h-5" />
|
||||||
|
Call (510) 701-2535
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
onClick={() => document.getElementById('services')?.scrollIntoView({ behavior: 'smooth' })}
|
||||||
|
className="flex items-center justify-center gap-2 rounded-lg border-2 border-white px-6 sm:px-8 py-3 sm:py-4 text-sm sm:text-base font-semibold text-white transition hover:bg-white hover:text-harbor-blue"
|
||||||
|
>
|
||||||
|
<Wrench className="w-4 h-4 sm:w-5 sm:h-5" />
|
||||||
|
View Our Services
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Scroll Indicator - Now visible on all screen sizes */}
|
||||||
|
<div
|
||||||
|
className="absolute bottom-8 cursor-pointer flex flex-col items-center"
|
||||||
|
onClick={() => document.getElementById('services')?.scrollIntoView({ behavior: 'smooth' })}
|
||||||
|
>
|
||||||
|
<p className="mb-2 text-xs md:text-sm uppercase tracking-widest opacity-80">Scroll to explore</p>
|
||||||
|
<ChevronDown className="h-5 w-5 md:h-6 md:w-6 animate-bounce" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
99
components/ServicesSection.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Check } from 'lucide-react'
|
||||||
|
|
||||||
|
const services = [
|
||||||
|
{
|
||||||
|
title: 'Hull Cleaning & Anode Change',
|
||||||
|
description: 'Professional underwater hull cleaning and zinc anode maintenance for optimal performance and corrosion protection.',
|
||||||
|
image: '/diver_cleaning.jpg',
|
||||||
|
features: [
|
||||||
|
'Removes marine growth',
|
||||||
|
'Improves fuel efficiency',
|
||||||
|
'Prevents corrosion',
|
||||||
|
'Marine-grade anodes'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Exterior Cleaning',
|
||||||
|
description: 'Complete exterior detailing to keep your boat looking pristine and protected.',
|
||||||
|
image: '/Washdown.jpg',
|
||||||
|
features: [
|
||||||
|
'Deep cleaning wash',
|
||||||
|
'Protective wax application',
|
||||||
|
'UV protection',
|
||||||
|
'Gel coat restoration'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Interior Detailing',
|
||||||
|
description: 'Thorough interior cleaning and conditioning for a fresh, comfortable cabin.',
|
||||||
|
image: '/Interior.jpg',
|
||||||
|
features: [
|
||||||
|
'Upholstery cleaning',
|
||||||
|
'Mold & mildew treatment',
|
||||||
|
'Surface conditioning',
|
||||||
|
'Odor elimination'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function ServicesSection() {
|
||||||
|
const handleQuote = () => {
|
||||||
|
window.location.href = 'tel:510-701-2535'
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="services" className="py-16 md:py-24 bg-gray-50">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold text-harbor-blue mb-4">Our Services</h2>
|
||||||
|
<p className="text-lg md:text-xl text-gray-600">Professional boat maintenance tailored to your needs</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8 max-w-7xl mx-auto">
|
||||||
|
{services.map((service, index) => (
|
||||||
|
<div key={index} className="bg-white rounded-xl shadow-lg overflow-hidden flex flex-col">
|
||||||
|
{/* Service Image */}
|
||||||
|
<div className="h-48 md:h-56 overflow-hidden">
|
||||||
|
<img
|
||||||
|
src={service.image}
|
||||||
|
alt={service.title}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Service Content */}
|
||||||
|
<div className="p-6 md:p-8 flex flex-col flex-1">
|
||||||
|
<h3 className="text-xl md:text-2xl font-bold text-harbor-blue mb-3">
|
||||||
|
{service.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-600 mb-6">
|
||||||
|
{service.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Features List */}
|
||||||
|
<ul className="space-y-3 mb-6 flex-1">
|
||||||
|
{service.features.map((feature, idx) => (
|
||||||
|
<li key={idx} className="flex items-start gap-2">
|
||||||
|
<Check className="w-5 h-5 text-harbor-gold mt-0.5 flex-shrink-0" />
|
||||||
|
<span className="text-gray-700">{feature}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{/* CTA Button */}
|
||||||
|
<button
|
||||||
|
onClick={handleQuote}
|
||||||
|
className="w-full bg-harbor-gold text-white font-semibold py-3 px-6 rounded-lg hover:bg-harbor-amber transition-colors"
|
||||||
|
>
|
||||||
|
Get Quote
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
29
components/TrustIndicators.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Award, Wrench, ShieldCheck } from 'lucide-react'
|
||||||
|
|
||||||
|
export default function TrustIndicators() {
|
||||||
|
return (
|
||||||
|
<section className="py-16 md:py-20 bg-white">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-8 md:gap-10 max-w-4xl mx-auto">
|
||||||
|
<div className="flex flex-col items-center text-center">
|
||||||
|
<Award className="w-12 h-12 text-harbor-gold mb-4" />
|
||||||
|
<span className="text-3xl md:text-4xl font-bold text-harbor-blue">20+</span>
|
||||||
|
<span className="text-sm md:text-base text-gray-600 mt-1">Years Experience</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center text-center">
|
||||||
|
<Wrench className="w-12 h-12 text-harbor-gold mb-4" />
|
||||||
|
<span className="text-3xl md:text-4xl font-bold text-harbor-blue">100%</span>
|
||||||
|
<span className="text-sm md:text-base text-gray-600 mt-1">Customizable Service</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center text-center col-span-2 md:col-span-1">
|
||||||
|
<ShieldCheck className="w-12 h-12 text-harbor-gold mb-4" />
|
||||||
|
<span className="text-3xl md:text-4xl font-bold text-harbor-blue">100%</span>
|
||||||
|
<span className="text-sm md:text-base text-gray-600 mt-1">Certified Experts</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
6
next-env.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
/// <reference path="./.next/types/routes.d.ts" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
9
next.config.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
images: {
|
||||||
|
domains: ['videos.pexels.com'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
||||||
2396
package-lock.json
generated
22
package.json
@@ -1,15 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "harborsmith",
|
"name": "harborsmith-nextjs",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
"type": "commonjs",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/test": "^1.55.0"
|
"@types/node": "^24.5.2",
|
||||||
|
"@types/react": "^19.1.13",
|
||||||
|
"@types/react-dom": "^19.1.9",
|
||||||
|
"autoprefixer": "^10.4.21",
|
||||||
|
"lucide-react": "^0.544.0",
|
||||||
|
"next": "^15.5.4",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"react": "^19.1.1",
|
||||||
|
"react-dom": "^19.1.1",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
|
"typescript": "^5.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 6.5 MiB After Width: | Height: | Size: 6.5 MiB |
|
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 2.7 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 5.1 MiB After Width: | Height: | Size: 5.1 MiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 3.7 MiB After Width: | Height: | Size: 3.7 MiB |
|
Before Width: | Height: | Size: 533 KiB After Width: | Height: | Size: 533 KiB |
|
Before Width: | Height: | Size: 4.5 MiB After Width: | Height: | Size: 4.5 MiB |
|
Before Width: | Height: | Size: 7.3 MiB After Width: | Height: | Size: 7.3 MiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 184 KiB |
|
Before Width: | Height: | Size: 4.2 MiB After Width: | Height: | Size: 4.2 MiB |
|
Before Width: | Height: | Size: 6.1 MiB After Width: | Height: | Size: 6.1 MiB |
|
Before Width: | Height: | Size: 4.4 MiB After Width: | Height: | Size: 4.4 MiB |
|
Before Width: | Height: | Size: 3.1 MiB After Width: | Height: | Size: 3.1 MiB |
|
Before Width: | Height: | Size: 4.1 MiB After Width: | Height: | Size: 4.1 MiB |
|
Before Width: | Height: | Size: 8.1 MiB After Width: | Height: | Size: 8.1 MiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 5.5 MiB After Width: | Height: | Size: 5.5 MiB |
|
Before Width: | Height: | Size: 6.4 MiB After Width: | Height: | Size: 6.4 MiB |
|
Before Width: | Height: | Size: 9.1 MiB After Width: | Height: | Size: 9.1 MiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 427 KiB After Width: | Height: | Size: 427 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
25
tailwind.config.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
|
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
|
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
|
],
|
||||||
|
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']
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
28
tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
BIN
vue-archive/apps/website/public/Anodes.jpg
Normal file
|
After Width: | Height: | Size: 6.5 MiB |
BIN
vue-archive/apps/website/public/Calendar.jpg
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
vue-archive/apps/website/public/ExtCleaning.jpg
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
vue-archive/apps/website/public/Foredeck.jpg
Normal file
|
After Width: | Height: | Size: 5.1 MiB |
BIN
vue-archive/apps/website/public/HARBOR-SMITH-white.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
vue-archive/apps/website/public/HARBOR-SMITH_navy.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
vue-archive/apps/website/public/Helm.jpg
Normal file
|
After Width: | Height: | Size: 3.7 MiB |
BIN
vue-archive/apps/website/public/Hull Clean Pricing.jpg
Normal file
|
After Width: | Height: | Size: 533 KiB |
BIN
vue-archive/apps/website/public/Interior.jpg
Normal file
|
After Width: | Height: | Size: 4.5 MiB |
BIN
vue-archive/apps/website/public/Licensed.jpg
Normal file
|
After Width: | Height: | Size: 7.3 MiB |
BIN
vue-archive/apps/website/public/QRCode-White-sm.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
vue-archive/apps/website/public/QRCode-White.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
vue-archive/apps/website/public/QRCode.png
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
vue-archive/apps/website/public/SpecialRequest.jpg
Normal file
|
After Width: | Height: | Size: 4.2 MiB |
BIN
vue-archive/apps/website/public/Washdown.jpg
Normal file
|
After Width: | Height: | Size: 6.1 MiB |
BIN
vue-archive/apps/website/public/Washdown2.jpg
Normal file
|
After Width: | Height: | Size: 4.4 MiB |
BIN
vue-archive/apps/website/public/Waxing.jpg
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
vue-archive/apps/website/public/diver_cleaning.jpg
Normal file
|
After Width: | Height: | Size: 4.1 MiB |
BIN
vue-archive/apps/website/public/diver_cleaning_2.jpg
Normal file
|
After Width: | Height: | Size: 8.1 MiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
BIN
vue-archive/apps/website/public/golden_gate.jpg
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
vue-archive/apps/website/public/iStock-2189089654.jpg
Normal file
|
After Width: | Height: | Size: 5.5 MiB |
BIN
vue-archive/apps/website/public/iStock-504868014.jpg
Normal file
|
After Width: | Height: | Size: 6.4 MiB |
BIN
vue-archive/apps/website/public/iStock-923244752.jpg
Normal file
|
After Width: | Height: | Size: 9.1 MiB |
BIN
vue-archive/apps/website/public/iStock-923244752b.jpg
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
vue-archive/apps/website/public/kid_1.jpeg
Normal file
|
After Width: | Height: | Size: 132 KiB |