11import React from "react" ;
22import { motion } from "framer-motion" ;
3+ import { Quote } from "lucide-react" ;
34import { Avatar , AvatarFallback , AvatarImage } from "../ui/avatar" ;
45import { useSafeColorMode } from "../../utils/useSafeColorMode" ;
56
@@ -23,95 +24,145 @@ const TestimonialCard: React.FC<TestimonialCardProps> = ({
2324 gradient,
2425 borderColor,
2526} ) => {
26- const { colorMode , isDark } = useSafeColorMode ( ) ;
27+ const { isDark } = useSafeColorMode ( ) ;
2728
28- const getBackgroundStyle = ( ) => {
29- let colorStop = "" ;
30- if ( gradient === "bg-pink-100" ) {
31- colorStop = "rgba(244, 194, 214, 0.35)" ; // Pink
32- } else if ( gradient === "bg-purple-100" ) {
33- colorStop = "rgba(191, 190, 255, 0.35)" ; // Blue/Lavender
34- } else {
35- colorStop = "rgba(165, 243, 252, 0.35)" ; // Cyan
36- }
29+ // Map gradient prop to card visual variant
30+ const isAccent = gradient === "bg-purple-100" ;
31+ const isFeatured = gradient === "bg-pink-100" ;
3732
38- return {
39- background : `
40- radial-gradient(
41- ellipse 600px 500px at 10% 85%,
42- ${ colorStop } 0%,
43- rgba(255, 255, 255, 0) 70%
44- ),
45- linear-gradient(
46- 135deg,
47- rgba(255, 255, 255, 0.95) 0%,
48- rgba(255, 255, 255, 0.9) 100%
49- )
50- ` ,
51- backdropFilter : "blur(4px)" ,
52- WebkitBackdropFilter : "blur(4px)" ,
53- border : "1px solid rgba(200, 200, 220, 0.4)" ,
54- } ;
33+ const getRole = ( ) => {
34+ if ( username === "VivienChen" ) return "Founder @ Toastie (BC Y24)" ;
35+ if ( username === "DanielHan" ) return "Founder @ Unsloth AI (YC W24, BC Y24)" ;
36+ if ( username === "EthanTrang" ) return "AI Engineer @ Relevance AI" ;
37+ return null ;
5538 } ;
5639
40+ const role = getRole ( ) ;
41+
42+ // Card colors by variant
43+ const cardBg = isAccent
44+ ? "linear-gradient(135deg, #4338ca 0%, #6d28d9 100%)"
45+ : isDark
46+ ? "#0a0a0a"
47+ : "#ffffff" ;
48+
49+ const isInverted = isAccent || isDark ;
50+ const textClr = isInverted ? "rgba(255,255,255,0.85)" : "rgba(17,24,39,0.78)" ;
51+ const nameClr = isInverted ? "#f1f5f9" : "#0f172a" ;
52+ const mutedClr = isAccent
53+ ? "rgba(255,255,255,0.50)"
54+ : isDark
55+ ? "rgba(148,163,184,0.55)"
56+ : "rgba(100,116,139,0.65)" ;
57+ const borderClr = isAccent
58+ ? "rgba(255,255,255,0.10)"
59+ : isDark
60+ ? "rgba(255,255,255,0.06)"
61+ : "rgba(0,0,0,0.07)" ;
62+ const quoteClr = isAccent
63+ ? "rgba(255,255,255,0.12)"
64+ : isDark
65+ ? "rgba(99,102,241,0.12)"
66+ : "rgba(99,102,241,0.08)" ;
67+
5768 return (
5869 < motion . div
59- initial = { { opacity : 0 , y : 20 } }
60- animate = { { opacity : 1 , y : 0 } }
61- exit = { { opacity : 0 , y : - 20 } }
62- whileHover = { { y : - 8 } }
63- transition = { { duration : 0.3 } }
64- className = { `group relative h-full w-full overflow-hidden rounded-3xl min-h-[550px] flex flex-col justify-between p-8` }
65- style = { getBackgroundStyle ( ) }
70+ whileHover = { { scale : 1.035 , y : - 3 } }
71+ transition = { { type : "spring" , stiffness : 400 , damping : 25 } }
72+ className = "relative flex flex-col justify-between overflow-hidden rounded-2xl p-5 flex-shrink-0 cursor-default select-none"
73+ style = { {
74+ width : 320 ,
75+ minHeight : 180 ,
76+ background : cardBg ,
77+ border : `1px solid ${ borderClr } ` ,
78+ boxShadow : isDark
79+ ? "0 2px 24px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.03)"
80+ : "0 2px 20px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)" ,
81+ } }
6682 >
67- { /* Subtle glossy overlay */ }
68- < div
69- className = "absolute inset-0 rounded-3xl pointer-events-none"
70- style = { {
71- background : `linear-gradient(135deg, rgba(255, 255, 255, 0.5) 0%, rgba(255, 255, 255, 0.2) 40%, transparent 70%)` ,
72- } }
73- />
74-
75- { /* Soft shadow */ }
76- < div className = "absolute inset-0 rounded-3xl pointer-events-none shadow-lg shadow-black/5" />
83+ { /* Grid pattern overlay for featured cards */ }
84+ { isFeatured && (
85+ < div
86+ className = "absolute inset-0 pointer-events-none"
87+ style = { {
88+ backgroundImage :
89+ "linear-gradient(to right, rgba(79,79,79,0.14) 1px, transparent 1px), linear-gradient(to bottom, rgba(79,79,79,0.14) 1px, transparent 1px)" ,
90+ backgroundSize : "44px 48px" ,
91+ maskImage :
92+ "radial-gradient(ellipse 80% 50% at 50% 0%, #000 70%, transparent 110%)" ,
93+ WebkitMaskImage :
94+ "radial-gradient(ellipse 80% 50% at 50% 0%, #000 70%, transparent 110%)" ,
95+ } }
96+ />
97+ ) }
7798
78- < div className = "relative z-10 flex flex-col h-full" >
79- { /* Testimonial Quote - Top Section */ }
80- < div className = "mb-12" >
81- < p className = "text-2xl leading-relaxed font-semibold text-gray-900 tracking-tight" >
82- "{ content . replace ( / # \w + / g, '' ) . trim ( ) } "
83- </ p >
84- </ div >
85-
86- { /* Avatar and Info - Bottom Section */ }
87- < div className = "mt-auto" >
88- < div className = "flex items-center gap-6" >
89- { /* Large Avatar with White Background */ }
90- < Avatar className = "h-20 w-20 overflow-hidden rounded-full border-3 border-white shadow-md flex-shrink-0 bg-white" >
91- < AvatarImage src = { avatar } className = "h-full w-full object-cover scale-125" />
92- < AvatarFallback className = "text-white font-bold text-lg bg-gradient-to-br from-purple-400 to-pink-400" >
93- { name . charAt ( 0 ) }
94- </ AvatarFallback >
95- </ Avatar >
99+ { /* Quote icon top-right */ }
100+ < div className = "absolute top-5 right-5 pointer-events-none" >
101+ < Quote
102+ size = { 28 }
103+ style = { { color : quoteClr } }
104+ className = "rotate-180"
105+ />
106+ </ div >
96107
97- < div className = "flex-1" >
98- < h3 className = "text-xl font-bold text-gray-900 mb-1 tracking-tight" >
99- { name }
100- </ h3 >
101- { username !== "AryanGupta" && username !== "DonaldAnyamba" && (
102- < p className = "text-sm text-gray-700 font-medium mb-3" >
103- { username === "VivienChen" ? "Founder @ Toastie (BC Y24)" :
104- username === "DanielHan" ? "Founder @ Unsloth AI (YC W24, BC Y24)" :
105- "AI Engineer @ Relevance AI" }
106- </ p >
107- ) }
108+ { /* Quote content */ }
109+ < p
110+ className = "relative z-10 text-[13px] leading-[1.65] flex-1 pr-8"
111+ style = { {
112+ color : textClr ,
113+ fontFamily : "'Inter', -apple-system, BlinkMacSystemFont, sans-serif" ,
114+ } }
115+ >
116+ “{ content . replace ( / # \w + / g, "" ) . trim ( ) } ”
117+ </ p >
108118
109- < div className = "flex items-center gap-3 text-xs text-gray-700" >
110- < span className = "font-medium" > { date } </ span >
111- </ div >
112- </ div >
113- </ div >
119+ { /* Author row */ }
120+ < div
121+ className = "relative z-10 flex items-center justify-between mt-3 pt-3"
122+ style = { { borderTop : `1px solid ${ borderClr } ` } }
123+ >
124+ < div className = "min-w-0 flex-1 mr-3" >
125+ < h3
126+ className = "font-semibold text-[14px] leading-tight truncate"
127+ style = { {
128+ color : nameClr ,
129+ fontFamily : "'Inter', -apple-system, sans-serif" ,
130+ } }
131+ >
132+ { name }
133+ </ h3 >
134+ { role && (
135+ < p
136+ className = "text-[12px] mt-0.5 truncate"
137+ style = { { color : mutedClr } }
138+ >
139+ { role }
140+ </ p >
141+ ) }
142+ < span
143+ className = "text-[11px] mt-0.5 inline-block"
144+ style = { { color : mutedClr } }
145+ >
146+ { date }
147+ </ span >
114148 </ div >
149+ < Avatar
150+ className = "h-9 w-9 rounded-lg overflow-hidden flex-shrink-0"
151+ style = { { border : `2px solid ${ borderClr } ` } }
152+ >
153+ < AvatarImage
154+ src = { avatar }
155+ className = "h-full w-full object-cover"
156+ />
157+ < AvatarFallback
158+ className = "text-white font-semibold text-sm rounded-xl"
159+ style = { {
160+ background : "linear-gradient(135deg, #6366f1, #a855f7)" ,
161+ } }
162+ >
163+ { name . charAt ( 0 ) }
164+ </ AvatarFallback >
165+ </ Avatar >
115166 </ div >
116167 </ motion . div >
117168 ) ;
0 commit comments