complete reskin of the frontend
This commit is contained in:
+54
-147
@@ -1,85 +1,66 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { MapPin, Share2, Heart } from "lucide-react";
|
||||
import { motion } from 'motion/react';
|
||||
import { Zap, Facebook, Instagram } from 'lucide-react';
|
||||
import PhoneMockup from './PhoneMockup';
|
||||
import { siFacebook, siInstagram, siTiktok } from 'simple-icons';
|
||||
|
||||
const Hero = () => {
|
||||
return (
|
||||
<section className="relative min-h-screen flex items-center justify-center pt-16 overflow-hidden">
|
||||
{/* Background decoration */}
|
||||
<div className="absolute inset-0 gradient-hero" />
|
||||
<div className="absolute top-1/4 -right-32 w-96 h-96 bg-primary/5 rounded-full blur-3xl" />
|
||||
<div className="absolute bottom-1/4 -left-32 w-80 h-80 bg-accent/10 rounded-full blur-3xl" />
|
||||
|
||||
<div className="container mx-auto px-6 py-20 relative z-10">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
{/* Badge */}
|
||||
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-primary/10 text-primary mb-8 animate-fade-up opacity-0">
|
||||
<span className="w-2 h-2 rounded-full bg-primary animate-pulse-soft" />
|
||||
<span className="text-sm font-medium font-body">The Strava for Dog Lovers</span>
|
||||
</div>
|
||||
<section className="relative pt-32 pb-20 overflow-hidden">
|
||||
<div className="max-w-7xl mx-auto px-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="text-left"
|
||||
>
|
||||
<div className="inline-flex items-center gap-2 bg-bg-secondary px-4 py-2 rounded-full mb-8 border border-border-subtle">
|
||||
<span className="text-xs font-bold uppercase tracking-widest text-accent-secondary">Simple and effective dog walking</span>
|
||||
</div>
|
||||
|
||||
{/* Main headline */}
|
||||
<h1 className="text-5xl md:text-6xl lg:text-7xl font-display font-bold text-foreground leading-tight mb-6 animate-fade-up opacity-0 stagger-1 text-balance">
|
||||
Every Walk Becomes an{" "}
|
||||
<span className="text-primary">Adventure</span>
|
||||
</h1>
|
||||
<h1 className="text-6xl md:text-7xl font-display font-extrabold tracking-tight mb-8 leading-[1.05] text-text-primary">
|
||||
Every walk <br />
|
||||
becomes <br />
|
||||
<span className="text-accent-secondary italic">an adventure.</span>
|
||||
</h1>
|
||||
|
||||
{/* Subheadline */}
|
||||
<p className="text-lg md:text-xl text-muted-foreground max-w-2xl mx-auto mb-10 font-body animate-fade-up opacity-0 stagger-2 text-balance">
|
||||
Record your dog walks, discover new routes, and connect with fellow dog owners.
|
||||
Track every tail wag, share your favorite trails, and build a community of happy pups.
|
||||
</p>
|
||||
<p className="text-text-primary/70 text-lg md:text-xl max-w-xl mb-12 font-medium leading-relaxed">
|
||||
Record your dog walks, discover new routes, and connect with fellow dog owners. Track every tail wag, share your favorite trails, and build a community of happy pups.
|
||||
</p>
|
||||
|
||||
{/* CTA Buttons */}
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 mb-16 animate-fade-up opacity-0 stagger-3">
|
||||
<Button variant="hero" size="xl" asChild>
|
||||
<a href="https://app.tailtrails.club" target="_blank" rel="noopener noreferrer">
|
||||
Start Walking Free
|
||||
<div className="flex flex-col sm:flex-row items-center gap-6 mb-12">
|
||||
<a href="/waitlist" className="w-full sm:w-auto bg-brand hover:bg-brand-light text-bg-primary px-10 py-5 rounded-2xl font-extrabold text-lg transition-all transform hover:scale-105 active:scale-95 flex items-center justify-center gap-2">
|
||||
Join Waitlist
|
||||
</a>
|
||||
</Button>
|
||||
<Button variant="hero-outline" size="xl" asChild>
|
||||
<a href="#how-it-works">
|
||||
See How It Works
|
||||
</div>
|
||||
|
||||
<div className="hidden sm:flex items-center gap-6">
|
||||
<a href="https://www.tiktok.com/@packlifeinportugal" className="w-10 h-10 glass rounded-full flex items-center justify-center text-text-secondary hover:bg-brand hover:text-bg-primary transition-colors">
|
||||
<svg className="w-5 h-5" viewBox="0 0 24 24" role="img" aria-hidden="true">
|
||||
<path d={siTiktok.path} fill="currentColor" />
|
||||
</svg>
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
<a href="https://www.facebook.com/packlifeinportugal" className="w-10 h-10 glass rounded-full flex items-center justify-center text-text-secondary hover:bg-brand hover:text-bg-primary transition-colors">
|
||||
<svg className="w-5 h-5" viewBox="0 0 24 24" role="img" aria-hidden="true">
|
||||
<path d={siFacebook.path} fill="currentColor" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://www.instagram.com/packlifeinportugal" className="w-10 h-10 glass rounded-full flex items-center justify-center text-text-secondary hover:bg-brand hover:text-bg-primary transition-colors">
|
||||
<svg className="w-5 h-5" viewBox="0 0 24 24" role="img" aria-hidden="true">
|
||||
<path d={siInstagram.path} fill="currentColor" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Feature pills */}
|
||||
<div className="flex flex-wrap items-center justify-center gap-4 animate-fade-up opacity-0 stagger-4">
|
||||
<div className="flex items-center gap-2 px-4 py-2 rounded-full bg-card shadow-soft">
|
||||
<MapPin className="w-4 h-4 text-primary" />
|
||||
<span className="text-sm font-body text-foreground">GPS Tracking</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 px-4 py-2 rounded-full bg-card shadow-soft">
|
||||
<Share2 className="w-4 h-4 text-primary" />
|
||||
<span className="text-sm font-body text-foreground">Share Routes</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 px-4 py-2 rounded-full bg-card shadow-soft">
|
||||
<Heart className="w-4 h-4 text-primary" />
|
||||
<span className="text-sm font-body text-foreground">Dog-Friendly Labels</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hero image/illustration area (now renders lazy-loading video preview) */}
|
||||
<div className="mt-16 relative max-w-5xl mx-auto animate-fade-up opacity-0 stagger-5">
|
||||
<div className="relative rounded-2xl overflow-hidden shadow-elevated bg-card p-2">
|
||||
<div className="aspect-[16/9] rounded-xl bg-gradient-to-br from-cream-dark to-cream overflow-hidden flex items-center justify-center">
|
||||
{/* Video preview container */}
|
||||
<VideoPreview />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Floating stats */}
|
||||
<div className="absolute -left-4 top-1/3 bg-card rounded-xl p-4 shadow-elevated animate-float hidden lg:block">
|
||||
<div className="text-2xl font-display font-bold text-primary">2.5k</div>
|
||||
<div className="text-sm text-muted-foreground font-body">Active Walkers</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute -right-4 top-1/2 bg-card rounded-xl p-4 shadow-elevated animate-float hidden lg:block" style={{ animationDelay: '0.5s' }}>
|
||||
<div className="text-2xl font-display font-bold text-primary">15k+</div>
|
||||
<div className="text-sm text-muted-foreground font-body">Routes Shared</div>
|
||||
</div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 30 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
className="relative"
|
||||
>
|
||||
<PhoneMockup />
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -87,77 +68,3 @@ const Hero = () => {
|
||||
};
|
||||
|
||||
export default Hero;
|
||||
|
||||
// Simple inline video preview component. Uses IntersectionObserver to lazy-load
|
||||
// the source only when the element is visible. Uses /hero.mp4 in the public folder.
|
||||
function VideoPreview() {
|
||||
const ref = useRef<HTMLVideoElement | null>(null);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
|
||||
const io = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) setVisible(true);
|
||||
});
|
||||
},
|
||||
{ threshold: 0.25 }
|
||||
);
|
||||
|
||||
io.observe(el);
|
||||
return () => io.disconnect();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible || loaded || !ref.current) return;
|
||||
const v = ref.current;
|
||||
|
||||
// Create sources dynamically to prevent early download
|
||||
const webmSrc = "/hero.webm"; // optional: if you generate a webm
|
||||
const mp4Src = "/hero.mp4";
|
||||
|
||||
// prefer webm if available (browser will pick compatible source)
|
||||
if (webmSrc) {
|
||||
const s1 = document.createElement("source");
|
||||
s1.src = webmSrc;
|
||||
s1.type = "video/webm";
|
||||
v.appendChild(s1);
|
||||
}
|
||||
const s2 = document.createElement("source");
|
||||
s2.src = mp4Src;
|
||||
s2.type = "video/mp4";
|
||||
v.appendChild(s2);
|
||||
|
||||
setLoaded(true);
|
||||
v.load();
|
||||
v.play().catch(() => {
|
||||
// autoplay may be blocked; that's fine
|
||||
});
|
||||
}, [visible, loaded]);
|
||||
|
||||
const toggle = () => {
|
||||
const v = ref.current;
|
||||
if (!v) return;
|
||||
if (v.paused) v.play().catch(() => {});
|
||||
else v.pause();
|
||||
};
|
||||
|
||||
return (
|
||||
<video
|
||||
ref={ref}
|
||||
poster="/hero-poster.jpg"
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
autoPlay
|
||||
preload="none"
|
||||
onClick={toggle}
|
||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||
aria-label="App preview"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user