Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 649b796ee1 | |||
| 490ba68fe0 | |||
| 896c694662 | |||
| 8dabc243d4 | |||
| da75632918 | |||
| d5200e8504 | |||
| 8d6bbad260 | |||
| 4fa640318a | |||
| 3206659f83 | |||
| 7722a14781 | |||
| 8323a15bbe | |||
| 8305b65f81 |
@@ -2,6 +2,7 @@
|
||||
|
||||
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
|
||||
import NavbarStyleApple from '@/components/navbar/NavbarStyleApple/NavbarStyleApple';
|
||||
import { useState, useEffect } from 'react';
|
||||
import HeroOverlay from '@/components/sections/hero/HeroOverlay';
|
||||
import InlineImageSplitTextAbout from '@/components/sections/about/InlineImageSplitTextAbout';
|
||||
import FeatureBento from '@/components/sections/feature/FeatureBento';
|
||||
@@ -9,9 +10,37 @@ import MetricCardSeven from '@/components/sections/metrics/MetricCardSeven';
|
||||
import TestimonialCardSixteen from '@/components/sections/testimonial/TestimonialCardSixteen';
|
||||
import ContactSplitForm from '@/components/sections/contact/ContactSplitForm';
|
||||
import FooterBaseCard from '@/components/sections/footer/FooterBaseCard';
|
||||
import AnimatedCursor from '@/components/AnimatedCursor';
|
||||
import LofiMusicPlayer from '@/components/LofiMusicPlayer';
|
||||
import { Sparkles, TrendingUp } from "lucide-react";
|
||||
|
||||
export default function LandingPage() {
|
||||
useEffect(() => {
|
||||
const playClickSound = () => {
|
||||
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
|
||||
const oscillator = audioContext.createOscillator();
|
||||
const gainNode = audioContext.createGain();
|
||||
|
||||
oscillator.connect(gainNode);
|
||||
gainNode.connect(audioContext.destination);
|
||||
|
||||
oscillator.frequency.value = 800;
|
||||
oscillator.type = 'sine';
|
||||
|
||||
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);
|
||||
|
||||
oscillator.start(audioContext.currentTime);
|
||||
oscillator.stop(audioContext.currentTime + 0.1);
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
playClickSound();
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleClick);
|
||||
return () => document.removeEventListener('click', handleClick);
|
||||
}, []);
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="hover-bubble"
|
||||
@@ -25,6 +54,8 @@ export default function LandingPage() {
|
||||
secondaryButtonStyle="radial-glow"
|
||||
headingFontWeight="medium"
|
||||
>
|
||||
<AnimatedCursor />
|
||||
<LofiMusicPlayer />
|
||||
<div id="nav" data-section="nav">
|
||||
<NavbarStyleApple
|
||||
brandName="Webild"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
"use client"
|
||||
|
||||
import { memo, useState, useRef, useEffect } from "react";
|
||||
import CardStack from "@/components/cardStack/CardStack";
|
||||
import MediaContent from "@/components/shared/MediaContent";
|
||||
import { cls } from "@/lib/utils";
|
||||
@@ -83,44 +85,182 @@ const TestimonialCard = memo(({
|
||||
overlayClassName = "",
|
||||
ratingClassName = "",
|
||||
nameClassName = "",
|
||||
roleClassName = "",
|
||||
roleClassName = "",
|
||||
companyClassName = "",
|
||||
}: TestimonialCardProps) => {
|
||||
return (
|
||||
<div className={cls("relative h-full aspect-[8/10] rounded-theme-capped overflow-hidden group", cardClassName)}>
|
||||
<MediaContent
|
||||
imageSrc={testimonial.imageSrc}
|
||||
videoSrc={testimonial.videoSrc}
|
||||
imageAlt={testimonial.imageAlt || testimonial.name}
|
||||
videoAriaLabel={testimonial.videoAriaLabel || testimonial.name}
|
||||
imageClassName={cls("relative z-1 w-full h-full object-cover!", imageClassName)}
|
||||
/>
|
||||
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
|
||||
const [scrollY, setScrollY] = useState(0);
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
<div className={cls("!absolute z-1 bottom-6 left-6 right-6 card backdrop-blur-xs p-6 flex flex-col gap-3 rounded-theme-capped", overlayClassName)}>
|
||||
<div className={cls("relative z-1 flex gap-1", ratingClassName)}>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<Star
|
||||
key={index}
|
||||
className={cls(
|
||||
"h-5 w-auto text-accent",
|
||||
index < testimonial.rating ? "fill-accent" : "fill-transparent"
|
||||
)}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
))}
|
||||
useEffect(() => {
|
||||
const handleScroll = () => setScrollY(window.scrollY);
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!cardRef.current) return;
|
||||
const rect = cardRef.current.getBoundingClientRect();
|
||||
setMousePosition({
|
||||
x: e.clientX - rect.left,
|
||||
y: e.clientY - rect.top,
|
||||
});
|
||||
};
|
||||
|
||||
const parallaxOffset = scrollY * 0.05;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={cardRef}
|
||||
className={cls("relative h-full aspect-[8/10] rounded-theme-capped overflow-hidden group", cardClassName)}
|
||||
style={{animation: "cardEntrance 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards"}}
|
||||
onMouseMove={handleMouseMove}
|
||||
>
|
||||
<style>{`
|
||||
@keyframes cardEntrance {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px) scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes glowPulse {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 20px rgba(var(--accent-rgb), 0.3), 0 20px 40px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 40px rgba(var(--accent-rgb), 0.5), 0 25px 50px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
@keyframes rotateBorder {
|
||||
from {
|
||||
background-position: 0% 0%;
|
||||
}
|
||||
to {
|
||||
background-position: 360% 360%;
|
||||
}
|
||||
}
|
||||
@keyframes colorShift {
|
||||
0%, 100% {
|
||||
filter: hue-rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
filter: hue-rotate(15deg);
|
||||
}
|
||||
}
|
||||
@keyframes sparkle {
|
||||
0%, 100% {
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes subtleBounce {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
}
|
||||
.group:hover .testimonial-inner {
|
||||
animation: glowPulse 2s ease-in-out infinite, colorShift 3s ease-in-out infinite, subtleBounce 0.6s ease-in-out infinite;
|
||||
}
|
||||
.group:hover .rotating-border {
|
||||
animation: rotateBorder 3s linear infinite;
|
||||
}
|
||||
.sparkle-particle {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.sparkle-particle.active {
|
||||
animation: sparkle 0.8s ease-out forwards;
|
||||
}
|
||||
`}</style>
|
||||
<div
|
||||
className="testimonial-inner relative w-full h-full transition-all duration-500 ease-out group-hover:shadow-2xl group-hover:-translate-y-12 group-hover:scale-105 group-hover:[transform:perspective(1000px)_rotateX(2deg)_rotateY(-3deg)_translateY(-48px)_scale(1.05)]"
|
||||
style={{transform: `translateY(${parallaxOffset}px)`}}
|
||||
>
|
||||
{/* Rotating border effect */}
|
||||
<div
|
||||
className="rotating-border absolute inset-0 rounded-theme-capped pointer-events-none opacity-0 group-hover:opacity-100 transition-opacity duration-300"
|
||||
style={{
|
||||
background: "conic-gradient(from 0deg, rgba(var(--accent-rgb), 0.5), rgba(var(--accent-rgb), 0.1), rgba(var(--accent-rgb), 0.5))",
|
||||
backgroundSize: "200% 200%",
|
||||
}}
|
||||
/>
|
||||
{/* Sparkle particles */}
|
||||
{[...Array(6)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="sparkle-particle active"
|
||||
style={{
|
||||
left: `${mousePosition.x}px`,
|
||||
top: `${mousePosition.y}px`,
|
||||
width: "8px",
|
||||
height: "8px",
|
||||
background: "radial-gradient(circle, rgba(var(--accent-rgb), 0.8), transparent)",
|
||||
borderRadius: "50%",
|
||||
transform: `translate(-50%, -50%) rotate(${(i / 6) * 360}deg) translateX(20px)`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{/* Front of card */}
|
||||
<div className="absolute w-full h-full">
|
||||
<MediaContent
|
||||
imageSrc={testimonial.imageSrc}
|
||||
videoSrc={testimonial.videoSrc}
|
||||
imageAlt={testimonial.imageAlt || testimonial.name}
|
||||
videoAriaLabel={testimonial.videoAriaLabel || testimonial.name}
|
||||
imageClassName={cls("relative z-1 w-full h-full object-cover!", imageClassName)}
|
||||
/>
|
||||
|
||||
<div className={cls("!absolute z-1 bottom-6 left-6 right-6 card backdrop-blur-xs p-6 flex flex-col gap-3 rounded-theme-capped", overlayClassName)}>
|
||||
=======
|
||||
<div className={cls("relative z-1 flex gap-1", ratingClassName)}>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<Star
|
||||
key={index}
|
||||
className={cls(
|
||||
"h-5 w-auto text-accent",
|
||||
index < testimonial.rating ? "fill-accent" : "fill-transparent"
|
||||
)}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<h3 className={cls("relative z-1 text-2xl font-medium text-foreground leading-[1.1] mt-1", nameClassName)}>
|
||||
{testimonial.name}
|
||||
</h3>
|
||||
|
||||
<div className="relative z-1 flex flex-col gap-1">
|
||||
<p className={cls("text-base text-foreground leading-[1.1]", roleClassName)}>
|
||||
{testimonial.role}
|
||||
</p>
|
||||
<p className={cls("text-base text-foreground leading-[1.1]", companyClassName)}>
|
||||
{testimonial.company}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className={cls("relative z-1 text-2xl font-medium text-foreground leading-[1.1] mt-1", nameClassName)}>
|
||||
{testimonial.name}
|
||||
</h3>
|
||||
|
||||
<div className="relative z-1 flex flex-col gap-1">
|
||||
<p className={cls("text-base text-foreground leading-[1.1]", roleClassName)}>
|
||||
{testimonial.role}
|
||||
</p>
|
||||
<p className={cls("text-base text-foreground leading-[1.1]", companyClassName)}>
|
||||
{testimonial.company}
|
||||
</p>
|
||||
{/* Back of card */}
|
||||
<div style={{ backfaceVisibility: "hidden", transform: "rotateY(180deg)" }} className="absolute w-full h-full bg-gradient-to-br from-accent/10 to-accent/5 backdrop-blur-xs rounded-theme-capped p-6 flex flex-col justify-center items-center gap-4">
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-foreground/80 mb-4">
|
||||
{testimonial.testimonial || "Exceptional results and outstanding service delivery."}
|
||||
</p>
|
||||
<button className="px-6 py-2 bg-accent text-accent-foreground rounded-theme-capped font-medium text-sm hover:bg-accent/90 transition-colors duration-300">
|
||||
Learn More
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user