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