complete reskin of the frontend

This commit is contained in:
itsamejms
2026-03-19 18:16:14 +00:00
parent e253e1cbc5
commit d1f6711d87
77 changed files with 1793 additions and 9742 deletions
+54 -147
View File
@@ -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"
/>
);
}