11"use client" ;
22
3+ import { motion } from "framer-motion" ;
34import { Terminal } from "lucide-react" ;
4- import { motion } from "motion/react" ;
5- import { useMemo } from "react" ;
65import { Tweet } from "react-tweet" ;
76
87const TWEET_IDS = [
@@ -13,9 +12,11 @@ const TWEET_IDS = [
1312 "1933149770639614324" ,
1413 "1937599252173128103" ,
1514 "1930511724702285885" ,
15+ "1945204056063913989" ,
1616 "1912836377365905496" ,
1717 "1907817662215757853" ,
1818 "1933216760896934060" ,
19+ "1942558041704182158" ,
1920 "1937383786637094958" ,
2021 "1931709370003583004" ,
2122 "1929147326955704662" ,
@@ -27,29 +28,39 @@ const TWEET_IDS = [
2728 "1917640304758514093" ,
2829 "1907831059275735353" ,
2930 "1912924558522524039" ,
31+ "1945054982870282575" ,
3032 "1933150129738981383" ,
3133 "1911490975173607495" ,
3234 "1930104047845158972" ,
3335 "1913773945523953713" ,
36+ "1944937093387706572" ,
3437 "1904241046898556970" ,
3538 "1913834145471672652" ,
39+ "1946245671880966269" ,
3640 "1930514202260635807" ,
3741 "1931589579749892480" ,
3842 "1904144343125860404" ,
3943 "1917610656477348229" ,
4044 "1904215768272654825" ,
4145 "1931830211013718312" ,
46+ "1944895251811893680" ,
4247 "1913833079342522779" ,
4348 "1930449311848087708" ,
49+ "1942680754384953790" ,
4450 "1907723601731530820" ,
51+ "1944553262792810603" ,
4552 "1904233896851521980" ,
4653 "1930294868808515726" ,
54+ "1943290033383047237" ,
4755 "1913801258789491021" ,
4856 "1907841646513005038" ,
4957 "1904301540422070671" ,
58+ "1944208789617471503" ,
5059 "1912837026925195652" ,
5160 "1904338606409531710" ,
61+ "1942965795920679188" ,
5262 "1904318186750652606" ,
63+ "1943656585294643386" ,
5364 "1908568583799484519" ,
5465 "1913018977321693448" ,
5566 "1904179661086556412" ,
@@ -61,20 +72,18 @@ const TWEET_IDS = [
6172] ;
6273
6374export 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 ( ( ) => [ ] ) ;
6979
7080 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 ) ;
7483 } ) ;
7584
76- return [ col1 , col2 , col3 ] ;
77- } , [ ] ) ;
85+ return columns ;
86+ } ;
7887
7988 const containerVariants = {
8089 hidden : { opacity : 0 } ,
@@ -92,8 +101,43 @@ export default function Testimonials() {
92101 } ,
93102 } ;
94103
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+
95139 return (
96- < div className = "mb-12" >
140+ < div className = "mb-12 w-full max-w-full overflow-hidden px-4 " >
97141 < div className = "mb-6 flex flex-wrap items-center justify-between gap-2 sm:flex-nowrap" >
98142 < div className = "flex items-center gap-2" >
99143 < Terminal className = "h-5 w-5 text-primary" />
@@ -122,114 +166,74 @@ export default function Testimonials() {
122166 </ div >
123167 </ div >
124168
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" >
131170 < 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"
134175 >
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+ ) ) }
163179 </ motion . div >
180+ </ div >
164181
182+ < div className = "hidden sm:block lg:hidden" >
165183 < 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"
168188 >
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+ ) ) }
197207 </ motion . div >
208+ </ div >
198209
210+ < div className = "hidden lg:block" >
199211 < 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"
202216 >
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+ ) ) }
231235 </ motion . div >
232- </ motion . div >
236+ </ div >
233237 </ div >
234238 ) ;
235239}
0 commit comments