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>
This commit is contained in:
Matt
2025-09-26 16:12:00 +02:00
parent 256f7eb069
commit d0f33f66f3
109 changed files with 3113 additions and 43 deletions

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

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

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

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