@@ -60,8 +60,9 @@ const FeaturedStrip: React.FC<{ onSelect: (p: Project) => void }> = ({ onSelect
6060
6161 const interval = setInterval ( ( ) => {
6262 if ( ! isHoveredRef . current && scrollRef . current ) {
63- // Card width: w-96 (384px) + gap-4 (16px) = 400px
64- const cardWidth = 400 ;
63+ // Measure actual card width + gap dynamically for responsiveness
64+ const firstCard = scrollRef . current . firstElementChild as HTMLElement | null ;
65+ const cardWidth = firstCard ? firstCard . offsetWidth + 16 : 336 ;
6566 scrollRef . current . scrollBy ( { left : cardWidth , behavior : 'smooth' } ) ;
6667
6768 // If reached the end, scroll back to start
@@ -136,12 +137,26 @@ const CarouselView: React.FC<{
136137 const [ currentIndex , setCurrentIndex ] = useState ( 0 ) ;
137138 const containerRef = useRef < HTMLDivElement > ( null ) ;
138139
139- const maxIndex = Math . max ( 0 , projects . length - columns ) ;
140+ // Responsive column override: force fewer columns on small screens
141+ const [ effectiveCols , setEffectiveCols ] = useState ( columns ) ;
142+ useEffect ( ( ) => {
143+ const update = ( ) => {
144+ const w = window . innerWidth ;
145+ if ( w < 640 ) setEffectiveCols ( 1 ) ;
146+ else if ( w < 1024 ) setEffectiveCols ( Math . min ( columns , 2 ) as 1 | 2 | 3 ) ;
147+ else setEffectiveCols ( columns ) ;
148+ } ;
149+ update ( ) ;
150+ window . addEventListener ( 'resize' , update ) ;
151+ return ( ) => window . removeEventListener ( 'resize' , update ) ;
152+ } , [ columns ] ) ;
153+
154+ const maxIndex = Math . max ( 0 , projects . length - effectiveCols ) ;
140155
141156 // Reset when filter or columns change
142157 useEffect ( ( ) => {
143158 setCurrentIndex ( 0 ) ;
144- } , [ projects . length , columns ] ) ;
159+ } , [ projects . length , effectiveCols ] ) ;
145160
146161 const go = useCallback (
147162 ( dir : 1 | - 1 ) => {
@@ -161,9 +176,9 @@ const CarouselView: React.FC<{
161176
162177 if ( projects . length === 0 ) return null ;
163178
164- // Each card takes 1/columns of the container width, with gap
179+ // Each card takes 1/effectiveCols of the container width, with gap
165180 const gapPx = 24 ;
166- const cardPercent = 100 / columns ;
181+ const cardPercent = 100 / effectiveCols ;
167182 const offsetPercent = - ( currentIndex * cardPercent ) ;
168183
169184 return (
@@ -202,7 +217,7 @@ const CarouselView: React.FC<{
202217 key = { project . id }
203218 onClick = { ( ) => onSelect ( project ) }
204219 className = "bg-white dark:bg-surface-800 border border-surface-200 dark:border-surface-700 rounded-xl overflow-hidden hover:shadow-lg dark:hover:border-surface-600 transition-shadow cursor-pointer flex-shrink-0"
205- style = { { width : `calc(${ cardPercent } % - ${ gapPx * ( columns - 1 ) / columns } px)` } }
220+ style = { { width : `calc(${ cardPercent } % - ${ gapPx * ( effectiveCols - 1 ) / effectiveCols } px)` } }
206221 >
207222 < ProjectThumbnail project = { project } className = "h-44" />
208223 < div className = "p-5" >
@@ -261,24 +276,26 @@ const CarouselView: React.FC<{
261276 </ div >
262277 </ div >
263278
264- { /* Progress indicator */ }
265- < div className = "flex items-center gap-1.5 mt-6" >
266- { Array . from ( { length : maxIndex + 1 } ) . map ( ( _ , i ) => (
267- < button
268- key = { i }
269- type = "button"
270- onClick = { ( ) => setCurrentIndex ( i ) }
271- className = { `rounded-full transition-all duration-200 ${
272- i === currentIndex
273- ? 'w-6 h-2 bg-surface-900 dark:bg-white'
274- : 'w-2 h-2 bg-surface-300 dark:bg-surface-600 hover:bg-surface-400 dark:hover:bg-surface-500'
275- } `}
276- aria-label = { `Go to position ${ i + 1 } ` }
277- />
278- ) ) }
279- </ div >
279+ { /* Progress indicator - dots for <=10 positions, text-only otherwise */ }
280+ { maxIndex + 1 <= 10 && (
281+ < div className = "flex items-center gap-1.5 mt-6" >
282+ { Array . from ( { length : maxIndex + 1 } ) . map ( ( _ , i ) => (
283+ < button
284+ key = { i }
285+ type = "button"
286+ onClick = { ( ) => setCurrentIndex ( i ) }
287+ className = { `rounded-full transition-all duration-200 ${
288+ i === currentIndex
289+ ? 'w-6 h-2 bg-surface-900 dark:bg-white'
290+ : 'w-2 h-2 bg-surface-300 dark:bg-surface-600 hover:bg-surface-400 dark:hover:bg-surface-500'
291+ } `}
292+ aria-label = { `Go to position ${ i + 1 } ` }
293+ />
294+ ) ) }
295+ </ div >
296+ ) }
280297 < p className = "text-xs text-surface-400 mt-3" >
281- { currentIndex + 1 } –{ Math . min ( currentIndex + columns , projects . length ) } of { projects . length }
298+ { currentIndex + 1 } –{ Math . min ( currentIndex + effectiveCols , projects . length ) } of { projects . length }
282299 </ p >
283300 </ div >
284301 ) ;
@@ -326,7 +343,7 @@ const TableView: React.FC<{
326343 < td className = "px-5 py-6" >
327344 < ProjectThumbnail
328345 project = { project }
329- className = "w-72 h-48 rounded-lg flex-shrink-0"
346+ className = "w-40 h-28 sm:w-56 sm:h-36 lg:w- 72 lg: h-48 rounded-lg flex-shrink-0"
330347 />
331348 </ td >
332349 < td className = "px-5 py-6" >
@@ -810,7 +827,7 @@ const Projects: React.FC = () => {
810827 </ ul >
811828 </ div >
812829
813- < div className = "flex items-center gap-3 pt-6 border-t border-surface-100 dark:border-surface-800" >
830+ < div className = "flex flex-wrap items-center gap-3 pt-6 border-t border-surface-100 dark:border-surface-800" >
814831 < a
815832 href = { selectedProject . githubUrl }
816833 target = "_blank"
0 commit comments