Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5280d6c07a | |||
| 9a15d5b959 | |||
| 887f24ecda | |||
| 2d1b894eab | |||
| cf73b46800 | |||
| 78c98816bb | |||
| b9fae9dc4b | |||
| 338c2ffeff | |||
| aed064ca08 | |||
| 9f9b653fd6 | |||
| 3f4c60effa | |||
| 412618400b |
@@ -10,6 +10,7 @@ import TestimonialCardOne from '@/components/sections/testimonial/TestimonialCar
|
||||
import ContactSplitForm from '@/components/sections/contact/ContactSplitForm';
|
||||
import FooterSimple from '@/components/sections/footer/FooterSimple';
|
||||
import { Award, BookOpen, Heart, Star, Trophy, Users, Zap } from 'lucide-react';
|
||||
import HeroBillboardCarousel from '@/components/sections/hero/HeroBillboardCarousel';
|
||||
|
||||
export default function SitePage() {
|
||||
return (
|
||||
@@ -34,24 +35,25 @@ export default function SitePage() {
|
||||
</div>
|
||||
|
||||
<div id="hero-section" data-section="hero-section">
|
||||
<HeroBillboard
|
||||
<HeroBillboardCarousel
|
||||
title="Elevating Excellence: Athlete & Scholar"
|
||||
description="Dedicated to achieving greatness both on the field and in the classroom. Committed to academic rigor and athletic prowess, ready to contribute to your institution's legacy of excellence."
|
||||
background={{ variant: 'radial-gradient' }}
|
||||
tag="College Recruiting Ready"
|
||||
tagIcon={Trophy}
|
||||
tagAnimation="slide-up"
|
||||
buttons={[{text:"View Achievements",href:"#achievements-section"},{text:"Contact Me",href:"#contact-section"}]}
|
||||
buttons={[
|
||||
{ text: "View Achievements", href: "#achievements-section", dataWebildId: "btn_view_achievements" },
|
||||
{ text: "Contact Me", href: "#contact-section", dataWebildId: "btn_contact_me" }
|
||||
]}
|
||||
buttonAnimation="slide-up"
|
||||
avatars={[{src:"http://img.b2bpic.net/free-photo/portrait-smiling-young-male-athlete-standing-race-track_23-2148162079.jpg",alt:"Athletic Performance Metric"},{src:"http://img.b2bpic.net/free-photo/two-female-sprinter-athletes-running-treadmill-race-training-athletics-stadium_613910-19085.jpg",alt:"Academic Excellence Indicator"}]}
|
||||
avatarText="2023 Academic All-American • 4.2 GPA"
|
||||
imageSrc="http://img.b2bpic.net/free-photo/portrait-half-naked-healthy-sportsman-starting-run_171337-9443.jpg"
|
||||
imageAlt="Professional portrait of athlete in uniform with academic achievements displayed"
|
||||
mediaAnimation="slide-up"
|
||||
marqueeItems={[{type: 'text-icon', text:"National Championship Participant",icon:Award},{type: 'text-icon', text:"Dean's List Recipient",icon:BookOpen},{type: 'text-icon', text:"Team Captain",icon:Users}]}
|
||||
marqueeSpeed={40}
|
||||
showMarqueeCard={true}
|
||||
ariaLabel="Hero section highlighting athletic and academic excellence"
|
||||
mediaItems={[
|
||||
{ imageSrc: "http://img.b2bpic.net/free-photo/shirtless-man-practicing-parkour-outdoors_52683-114636.jpg", imageAlt: "Athlete training outdoors with intensity" },
|
||||
{ imageSrc: "http://img.b2bpic.net/free-photo/full-shot-fit-man-training-outdoors_23-2150351926.jpg", imageAlt: "Professional athlete in training session" },
|
||||
{ imageSrc: "http://img.b2bpic.net/free-photo/man-ready-sport-night_23-2149304841.jpg", imageAlt: "Athlete preparing for competition" },
|
||||
{ imageSrc: "http://img.b2bpic.net/free-photo/full-shot-man-tying-shoelaces_23-2149142343.jpg", imageAlt: "Athlete focused on performance preparation" }
|
||||
]}
|
||||
ariaLabel="Hero carousel showcasing athletic excellence and training dedication"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef } from "react";
|
||||
import TextBox from "@/components/Textbox";
|
||||
import MediaContent from "@/components/shared/MediaContent";
|
||||
import AutoCarousel from "@/components/cardStack/layouts/carousels/AutoCarousel";
|
||||
@@ -44,6 +45,8 @@ interface HeroBillboardCarouselProps {
|
||||
buttons?: ButtonConfig[];
|
||||
buttonAnimation?: ButtonAnimationType;
|
||||
mediaItems: MediaItem[];
|
||||
backgroundImageSrc?: string;
|
||||
backgroundImageOpacity?: number;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
@@ -67,6 +70,8 @@ const HeroBillboardCarousel = ({
|
||||
buttons,
|
||||
buttonAnimation,
|
||||
mediaItems,
|
||||
backgroundImageSrc,
|
||||
backgroundImageOpacity = 0.3,
|
||||
ariaLabel = "Hero section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
@@ -79,6 +84,8 @@ const HeroBillboardCarousel = ({
|
||||
buttonTextClassName = "",
|
||||
mediaWrapperClassName = "",
|
||||
}: HeroBillboardCarouselProps) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const carouselRef = useRef<HTMLDivElement>(null);
|
||||
const renderCarouselItem = (item: MediaItem, index: number) => (
|
||||
<div
|
||||
key={index}
|
||||
@@ -98,11 +105,24 @@ const HeroBillboardCarousel = ({
|
||||
<section
|
||||
aria-label={ariaLabel}
|
||||
className={cls(
|
||||
"relative w-full py-hero-page-padding md:h-svh md:py-0",
|
||||
"relative w-full py-hero-page-padding md:h-svh md:py-0 overflow-hidden",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<HeroBackgrounds {...background} />
|
||||
{backgroundImageSrc && (
|
||||
<div
|
||||
className="absolute inset-0 z-0"
|
||||
style={{
|
||||
backgroundImage: `url('${backgroundImageSrc}')`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
opacity: backgroundImageOpacity,
|
||||
filter: 'blur(40px)',
|
||||
}}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<div className={cls(
|
||||
"mx-auto flex flex-col gap-14 md:gap-10 relative z-10",
|
||||
"w-full md:w-content-width md:h-full md:items-center md:justify-center",
|
||||
@@ -129,7 +149,12 @@ const HeroBillboardCarousel = ({
|
||||
center={true}
|
||||
/>
|
||||
|
||||
<div className={cls("w-full -mx-[var(--content-padding)]", mediaWrapperClassName)}>
|
||||
<div
|
||||
ref={carouselRef}
|
||||
className={cls("w-full -mx-[var(--content-padding)] relative z-20", mediaWrapperClassName)}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<AutoCarousel
|
||||
title=""
|
||||
description=""
|
||||
@@ -141,6 +166,8 @@ const HeroBillboardCarousel = ({
|
||||
itemClassName="!w-55 md:!w-carousel-item-4"
|
||||
ariaLabel="Hero carousel"
|
||||
showTextBox={false}
|
||||
autoPlayInterval={isHovered ? 0 : 5000}
|
||||
pauseOnHover={true}
|
||||
>
|
||||
{mediaItems?.map(renderCarouselItem)}
|
||||
</AutoCarousel>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { memo, useState } from "react";
|
||||
import CardStack from "@/components/cardStack/CardStack";
|
||||
import MediaContent from "@/components/shared/MediaContent";
|
||||
import { cls } from "@/lib/utils";
|
||||
@@ -8,6 +8,82 @@ import { Star } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, ButtonAnimationType, CardAnimationTypeWith3D, GridVariant, TitleSegment, TextboxLayout, InvertedBackground } from "@/components/cardStack/types";
|
||||
|
||||
const TestimonialCardFlip = memo(({ testimonial, className }: { testimonial: Testimonial; className?: string }) => {
|
||||
const [isFlipped, setIsFlipped] = useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cls(
|
||||
"h-full cursor-pointer perspective",
|
||||
className
|
||||
)}
|
||||
onClick={() => setIsFlipped(!isFlipped)}
|
||||
onMouseEnter={() => setIsFlipped(true)}
|
||||
onMouseLeave={() => setIsFlipped(false)}
|
||||
style={{
|
||||
perspective: "1000px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
transformStyle: "preserve-3d",
|
||||
transform: isFlipped ? "rotateY(180deg)" : "rotateY(0deg)",
|
||||
transition: "transform 0.6s ease-in-out",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{/* Front side - Name and Photo */}
|
||||
<div
|
||||
style={{
|
||||
backfaceVisibility: "hidden",
|
||||
WebkitBackfaceVisibility: "hidden",
|
||||
}}
|
||||
className="w-full h-full flex flex-col items-center justify-center p-6 bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 rounded-lg border border-slate-200 dark:border-slate-700"
|
||||
>
|
||||
{testimonial.imageSrc && (
|
||||
<img
|
||||
src={testimonial.imageSrc}
|
||||
alt={testimonial.imageAlt || testimonial.name}
|
||||
className="w-24 h-24 rounded-full object-cover mb-4 border-4 border-white dark:border-slate-700 shadow-lg"
|
||||
/>
|
||||
)}
|
||||
<h3 className="text-lg font-semibold text-slate-900 dark:text-white text-center">
|
||||
{testimonial.name}
|
||||
</h3>
|
||||
<p className="text-sm text-slate-600 dark:text-slate-400 text-center mt-1">
|
||||
{testimonial.role}
|
||||
</p>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-500 text-center mt-1">
|
||||
{testimonial.company}
|
||||
</p>
|
||||
<div className="flex gap-1 mt-3">
|
||||
{Array.from({ length: testimonial.rating }).map((_, i) => (
|
||||
<Star key={i} size={16} className="fill-yellow-400 text-yellow-400" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Back side - Testimonial Text */}
|
||||
<div
|
||||
style={{
|
||||
backfaceVisibility: "hidden",
|
||||
WebkitBackfaceVisibility: "hidden",
|
||||
transform: "rotateY(180deg)",
|
||||
}}
|
||||
className="absolute inset-0 w-full h-full flex flex-col items-center justify-center p-6 bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-blue-900 dark:to-indigo-900 rounded-lg border border-blue-200 dark:border-indigo-700 shadow-lg"
|
||||
>
|
||||
<p className="text-sm text-slate-700 dark:text-slate-200 text-center leading-relaxed italic">
|
||||
"{testimonial.testimonialText || "No testimonial text provided"}"
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
TestimonialCardFlip.displayName = "TestimonialCardFlip";
|
||||
|
||||
type Testimonial = {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -18,6 +94,7 @@ type Testimonial = {
|
||||
videoSrc?: string;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
testimonialText?: string;
|
||||
};
|
||||
|
||||
interface TestimonialCardOneProps {
|
||||
@@ -50,6 +127,7 @@ interface TestimonialCardOneProps {
|
||||
nameClassName?: string;
|
||||
roleClassName?: string;
|
||||
companyClassName?: string;
|
||||
testimonialTextClassName?: string;
|
||||
gridClassName?: string;
|
||||
carouselClassName?: string;
|
||||
controlsClassName?: string;
|
||||
@@ -69,6 +147,7 @@ interface TestimonialCardProps {
|
||||
nameClassName?: string;
|
||||
roleClassName?: string;
|
||||
companyClassName?: string;
|
||||
testimonialTextClassName?: string;
|
||||
}
|
||||
|
||||
const TestimonialCard = memo(({
|
||||
@@ -80,41 +159,85 @@ const TestimonialCard = memo(({
|
||||
nameClassName = "",
|
||||
roleClassName = "",
|
||||
companyClassName = "",
|
||||
testimonialTextClassName = "",
|
||||
}: TestimonialCardProps) => {
|
||||
return (
|
||||
<div className={cls("relative h-full 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 [isFlipped, setIsFlipped] = useState(false);
|
||||
|
||||
<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}
|
||||
/>
|
||||
))}
|
||||
return (
|
||||
<div
|
||||
className={cls("relative h-full rounded-theme-capped overflow-hidden group cursor-pointer", cardClassName)}
|
||||
style={{
|
||||
perspective: "1000px",
|
||||
}}
|
||||
onMouseEnter={() => setIsFlipped(true)}
|
||||
onMouseLeave={() => setIsFlipped(false)}
|
||||
onClick={() => setIsFlipped(!isFlipped)}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
transformStyle: "preserve-3d",
|
||||
transform: isFlipped ? "rotateY(180deg)" : "rotateY(0deg)",
|
||||
transition: "transform 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55)",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{/* Front of card */}
|
||||
<div
|
||||
style={{
|
||||
backfaceVisibility: "hidden",
|
||||
WebkitBackfaceVisibility: "hidden",
|
||||
}}
|
||||
className="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}
|
||||
{/* Back of card */}
|
||||
<div
|
||||
style={{
|
||||
backfaceVisibility: "hidden",
|
||||
WebkitBackfaceVisibility: "hidden",
|
||||
transform: "rotateY(180deg)",
|
||||
}}
|
||||
className="absolute inset-0 w-full h-full card backdrop-blur-xs rounded-theme-capped p-6 flex flex-col justify-center items-center"
|
||||
>
|
||||
<p className={cls("text-base text-foreground leading-relaxed text-center", testimonialTextClassName)}>
|
||||
{testimonial.testimonialText || "No testimonial text provided"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -154,6 +277,7 @@ const TestimonialCardOne = ({
|
||||
nameClassName = "",
|
||||
roleClassName = "",
|
||||
companyClassName = "",
|
||||
testimonialTextClassName = "",
|
||||
gridClassName = "",
|
||||
carouselClassName = "",
|
||||
controlsClassName = "",
|
||||
@@ -208,6 +332,7 @@ const TestimonialCardOne = ({
|
||||
nameClassName={nameClassName}
|
||||
roleClassName={roleClassName}
|
||||
companyClassName={companyClassName}
|
||||
testimonialTextClassName={testimonialTextClassName}
|
||||
/>
|
||||
))}
|
||||
</CardStack>
|
||||
|
||||
Reference in New Issue
Block a user