1
1
"use client" ;
2
2
3
+ import { motion } from "framer-motion" ;
3
4
import { Terminal } from "lucide-react" ;
4
- import { motion } from "motion/react" ;
5
- import { useMemo } from "react" ;
6
5
import { Tweet } from "react-tweet" ;
7
6
8
7
const TWEET_IDS = [
@@ -13,9 +12,11 @@ const TWEET_IDS = [
13
12
"1933149770639614324" ,
14
13
"1937599252173128103" ,
15
14
"1930511724702285885" ,
15
+ "1945204056063913989" ,
16
16
"1912836377365905496" ,
17
17
"1907817662215757853" ,
18
18
"1933216760896934060" ,
19
+ "1942558041704182158" ,
19
20
"1937383786637094958" ,
20
21
"1931709370003583004" ,
21
22
"1929147326955704662" ,
@@ -27,29 +28,39 @@ const TWEET_IDS = [
27
28
"1917640304758514093" ,
28
29
"1907831059275735353" ,
29
30
"1912924558522524039" ,
31
+ "1945054982870282575" ,
30
32
"1933150129738981383" ,
31
33
"1911490975173607495" ,
32
34
"1930104047845158972" ,
33
35
"1913773945523953713" ,
36
+ "1944937093387706572" ,
34
37
"1904241046898556970" ,
35
38
"1913834145471672652" ,
39
+ "1946245671880966269" ,
36
40
"1930514202260635807" ,
37
41
"1931589579749892480" ,
38
42
"1904144343125860404" ,
39
43
"1917610656477348229" ,
40
44
"1904215768272654825" ,
41
45
"1931830211013718312" ,
46
+ "1944895251811893680" ,
42
47
"1913833079342522779" ,
43
48
"1930449311848087708" ,
49
+ "1942680754384953790" ,
44
50
"1907723601731530820" ,
51
+ "1944553262792810603" ,
45
52
"1904233896851521980" ,
46
53
"1930294868808515726" ,
54
+ "1943290033383047237" ,
47
55
"1913801258789491021" ,
48
56
"1907841646513005038" ,
49
57
"1904301540422070671" ,
58
+ "1944208789617471503" ,
50
59
"1912837026925195652" ,
51
60
"1904338606409531710" ,
61
+ "1942965795920679188" ,
52
62
"1904318186750652606" ,
63
+ "1943656585294643386" ,
53
64
"1908568583799484519" ,
54
65
"1913018977321693448" ,
55
66
"1904179661086556412" ,
@@ -61,20 +72,18 @@ const TWEET_IDS = [
61
72
] ;
62
73
63
74
export default function Testimonials ( ) {
64
- // Split tweets into 3 columns
65
- const columns = useMemo ( ( ) => {
66
- const col1 : string [ ] = [ ] ;
67
- const col2 : string [ ] = [ ] ;
68
- const col3 : string [ ] = [ ] ;
75
+ const getResponsiveColumns = ( numCols : number ) => {
76
+ const columns : string [ ] [ ] = Array ( numCols )
77
+ . fill ( null )
78
+ . map ( ( ) => [ ] ) ;
69
79
70
80
TWEET_IDS . forEach ( ( tweetId , index ) => {
71
- if ( index % 3 === 0 ) col1 . push ( tweetId ) ;
72
- else if ( index % 3 === 1 ) col2 . push ( tweetId ) ;
73
- else col3 . push ( tweetId ) ;
81
+ const colIndex = index % numCols ;
82
+ columns [ colIndex ] . push ( tweetId ) ;
74
83
} ) ;
75
84
76
- return [ col1 , col2 , col3 ] ;
77
- } , [ ] ) ;
85
+ return columns ;
86
+ } ;
78
87
79
88
const containerVariants = {
80
89
hidden : { opacity : 0 } ,
@@ -92,8 +101,43 @@ export default function Testimonials() {
92
101
} ,
93
102
} ;
94
103
104
+ const TweetCard = ( {
105
+ tweetId,
106
+ index,
107
+ } : {
108
+ tweetId : string ;
109
+ index : number ;
110
+ } ) => (
111
+ < motion . div
112
+ className = "w-full min-w-0"
113
+ initial = { { opacity : 0 , y : 20 , scale : 0.95 } }
114
+ animate = { { opacity : 1 , y : 0 , scale : 1 } }
115
+ transition = { {
116
+ delay : index * 0.05 ,
117
+ duration : 0.4 ,
118
+ ease : "easeOut" ,
119
+ } }
120
+ >
121
+ < div className = "terminal-block-hover w-full min-w-0 overflow-hidden rounded border border-border bg-background" >
122
+ < div className = "sticky top-0 z-10 border-border border-b bg-muted/20 px-3 py-2" >
123
+ < div className = "flex items-center gap-2" >
124
+ < span className = "text-primary text-xs" > ▶</ span >
125
+ < span className = "font-mono font-semibold text-xs" >
126
+ [TWEET_{ String ( index + 1 ) . padStart ( 3 , "0" ) } ]
127
+ </ span >
128
+ </ div >
129
+ </ div >
130
+ < div className = "w-full min-w-0 overflow-hidden" >
131
+ < div style = { { width : "100%" , minWidth : 0 , maxWidth : "100%" } } >
132
+ < Tweet id = { tweetId } />
133
+ </ div >
134
+ </ div >
135
+ </ div >
136
+ </ motion . div >
137
+ ) ;
138
+
95
139
return (
96
- < div className = "mb-12" >
140
+ < div className = "mb-12 w-full max-w-full overflow-hidden px-4 " >
97
141
< div className = "mb-6 flex flex-wrap items-center justify-between gap-2 sm:flex-nowrap" >
98
142
< div className = "flex items-center gap-2" >
99
143
< Terminal className = "h-5 w-5 text-primary" />
@@ -122,114 +166,74 @@ export default function Testimonials() {
122
166
</ div >
123
167
</ div >
124
168
125
- < motion . div
126
- className = "flex flex-col gap-4 sm:flex-row"
127
- variants = { containerVariants }
128
- initial = "hidden"
129
- animate = "visible"
130
- >
169
+ < div className = "block sm:hidden" >
131
170
< motion . div
132
- className = "flex flex-1 flex-col gap-4"
133
- variants = { columnVariants }
171
+ className = "flex flex-col gap-4"
172
+ variants = { containerVariants }
173
+ initial = "hidden"
174
+ animate = "visible"
134
175
>
135
- { columns [ 0 ] ?. map ( ( tweetId , tweetIndex ) => {
136
- const globalIndex = 0 + tweetIndex * 3 ;
137
- return (
138
- < motion . div
139
- key = { tweetId }
140
- className = "terminal-block-hover overflow-hidden rounded border border-border bg-background"
141
- initial = { { opacity : 0 , y : 20 , scale : 0.95 } }
142
- animate = { { opacity : 1 , y : 0 , scale : 1 } }
143
- transition = { {
144
- delay : tweetIndex * 0.05 ,
145
- duration : 0.4 ,
146
- ease : "easeOut" ,
147
- } }
148
- >
149
- < div className = "sticky top-0 z-10 border-border border-b bg-muted/20 px-3 py-2" >
150
- < div className = "flex items-center gap-2" >
151
- < span className = "text-primary text-xs" > ▶</ span >
152
- < span className = "font-mono font-semibold text-xs" >
153
- [TWEET_{ String ( globalIndex + 1 ) . padStart ( 3 , "0" ) } ]
154
- </ span >
155
- </ div >
156
- </ div >
157
- < div className = "p-0" >
158
- < Tweet id = { tweetId } />
159
- </ div >
160
- </ motion . div >
161
- ) ;
162
- } ) }
176
+ { TWEET_IDS . map ( ( tweetId , index ) => (
177
+ < TweetCard key = { tweetId } tweetId = { tweetId } index = { index } />
178
+ ) ) }
163
179
</ motion . div >
180
+ </ div >
164
181
182
+ < div className = "hidden sm:block lg:hidden" >
165
183
< motion . div
166
- className = "flex flex-1 flex-col gap-4"
167
- variants = { columnVariants }
184
+ className = "grid grid-cols-2 gap-4"
185
+ variants = { containerVariants }
186
+ initial = "hidden"
187
+ animate = "visible"
168
188
>
169
- { columns [ 1 ] ?. map ( ( tweetId , tweetIndex ) => {
170
- const globalIndex = 1 + tweetIndex * 3 ;
171
- return (
172
- < motion . div
173
- key = { tweetId }
174
- className = "terminal-block-hover overflow-hidden rounded border border-border bg-background"
175
- initial = { { opacity : 0 , y : 20 , scale : 0.95 } }
176
- animate = { { opacity : 1 , y : 0 , scale : 1 } }
177
- transition = { {
178
- delay : tweetIndex * 0.05 ,
179
- duration : 0.4 ,
180
- ease : "easeOut" ,
181
- } }
182
- >
183
- < div className = "sticky top-0 z-10 border-border border-b bg-muted/20 px-3 py-2" >
184
- < div className = "flex items-center gap-2" >
185
- < span className = "text-primary text-xs" > ▶</ span >
186
- < span className = "font-mono font-semibold text-xs" >
187
- [TWEET_{ String ( globalIndex + 1 ) . padStart ( 3 , "0" ) } ]
188
- </ span >
189
- </ div >
190
- </ div >
191
- < div className = "p-0" >
192
- < Tweet id = { tweetId } />
193
- </ div >
194
- </ motion . div >
195
- ) ;
196
- } ) }
189
+ { getResponsiveColumns ( 2 ) . map ( ( column , colIndex ) => (
190
+ < motion . div
191
+ key = { column . join ( "-" ) }
192
+ className = "flex min-w-0 flex-col gap-4"
193
+ variants = { columnVariants }
194
+ >
195
+ { column . map ( ( tweetId , tweetIndex ) => {
196
+ const globalIndex = colIndex + tweetIndex * 2 ;
197
+ return (
198
+ < TweetCard
199
+ key = { tweetId }
200
+ tweetId = { tweetId }
201
+ index = { globalIndex }
202
+ />
203
+ ) ;
204
+ } ) }
205
+ </ motion . div >
206
+ ) ) }
197
207
</ motion . div >
208
+ </ div >
198
209
210
+ < div className = "hidden lg:block" >
199
211
< motion . div
200
- className = "flex flex-1 flex-col gap-4"
201
- variants = { columnVariants }
212
+ className = "grid grid-cols-3 gap-4"
213
+ variants = { containerVariants }
214
+ initial = "hidden"
215
+ animate = "visible"
202
216
>
203
- { columns [ 2 ] ?. map ( ( tweetId , tweetIndex ) => {
204
- const globalIndex = 2 + tweetIndex * 3 ;
205
- return (
206
- < motion . div
207
- key = { tweetId }
208
- className = "terminal-block-hover overflow-hidden rounded border border-border bg-background"
209
- initial = { { opacity : 0 , y : 20 , scale : 0.95 } }
210
- animate = { { opacity : 1 , y : 0 , scale : 1 } }
211
- transition = { {
212
- delay : tweetIndex * 0.05 ,
213
- duration : 0.4 ,
214
- ease : "easeOut" ,
215
- } }
216
- >
217
- < div className = "sticky top-0 z-10 border-border border-b bg-muted/20 px-3 py-2" >
218
- < div className = "flex items-center gap-2" >
219
- < span className = "text-primary text-xs" > ▶</ span >
220
- < span className = "font-mono font-semibold text-xs" >
221
- [TWEET_{ String ( globalIndex + 1 ) . padStart ( 3 , "0" ) } ]
222
- </ span >
223
- </ div >
224
- </ div >
225
- < div className = "p-0" >
226
- < Tweet id = { tweetId } />
227
- </ div >
228
- </ motion . div >
229
- ) ;
230
- } ) }
217
+ { getResponsiveColumns ( 3 ) . map ( ( column , colIndex ) => (
218
+ < motion . div
219
+ key = { column . join ( "-" ) }
220
+ className = "flex min-w-0 flex-col gap-4"
221
+ variants = { columnVariants }
222
+ >
223
+ { column . map ( ( tweetId , tweetIndex ) => {
224
+ const globalIndex = colIndex + tweetIndex * 3 ;
225
+ return (
226
+ < TweetCard
227
+ key = { tweetId }
228
+ tweetId = { tweetId }
229
+ index = { globalIndex }
230
+ />
231
+ ) ;
232
+ } ) }
233
+ </ motion . div >
234
+ ) ) }
231
235
</ motion . div >
232
- </ motion . div >
236
+ </ div >
233
237
</ div >
234
238
) ;
235
239
}
0 commit comments