Skip to content

Commit 4383a35

Browse files
davidagustinclaude
andcommitted
fix: comprehensive mobile responsiveness across all components
- Carousel: auto-adapt columns for screen size (1 col on mobile, max 2 on tablet), hide progress dots when >10 positions - Navbar: solid background when mobile menu is open (fixes invisible dropdown items over dark hero) - Table: responsive thumbnail sizes (w-40 on mobile, w-72 on desktop) - Featured strip: dynamically measure card width for auto-scroll - Modal: flex-wrap on action buttons for narrow screens - About: responsive gap on stats row (gap-6 on mobile, gap-12 on sm+) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5e0279c commit 4383a35

3 files changed

Lines changed: 51 additions & 38 deletions

File tree

src/components/About.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ const About: React.FC = () => {
181181
</p>
182182

183183
{/* ---- stats ---- */}
184-
<div className="flex gap-12">
184+
<div className="flex gap-6 sm:gap-12">
185185
{stats.map((stat) => (
186186
<div key={stat.label}>
187187
<div className="text-3xl font-bold text-surface-900 dark:text-white">

src/components/Navbar.tsx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const Navbar: React.FC<NavbarProps> = ({ theme, toggleTheme }) => {
3737
<motion.nav
3838
aria-label="Main navigation"
3939
className={`fixed top-0 w-full z-50 transition-all duration-300 ${
40-
scrolled
40+
scrolled || isOpen
4141
? 'bg-white/95 dark:bg-surface-950/95 backdrop-blur-md shadow-sm border-b border-surface-100 dark:border-surface-800'
4242
: 'bg-transparent'
4343
}`}
@@ -53,7 +53,7 @@ const Navbar: React.FC<NavbarProps> = ({ theme, toggleTheme }) => {
5353
type="button"
5454
onClick={() => scrollToSection('home')}
5555
className={`text-lg font-bold tracking-tight transition-colors ${
56-
scrolled
56+
scrolled || isOpen
5757
? 'text-surface-900 dark:text-white hover:text-primary-700 dark:hover:text-primary-400'
5858
: 'text-white hover:text-primary-300'
5959
}`}
@@ -93,7 +93,7 @@ const Navbar: React.FC<NavbarProps> = ({ theme, toggleTheme }) => {
9393
onClick={toggleTheme}
9494
aria-label={theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
9595
className={`p-2 rounded-lg transition-colors duration-200 ${
96-
scrolled
96+
scrolled || isOpen
9797
? 'text-surface-500 dark:text-surface-400 hover:text-surface-900 dark:hover:text-white hover:bg-surface-100 dark:hover:bg-surface-800'
9898
: 'text-surface-300 hover:text-white hover:bg-white/10'
9999
}`}
@@ -112,21 +112,21 @@ const Navbar: React.FC<NavbarProps> = ({ theme, toggleTheme }) => {
112112
>
113113
<span
114114
className={`w-5 h-px transition-all duration-300 ${
115-
scrolled
115+
scrolled || isOpen
116116
? 'bg-surface-800 dark:bg-white'
117117
: 'bg-white'
118118
} ${isOpen ? 'rotate-45 translate-y-[3.5px]' : ''}`}
119119
/>
120120
<span
121121
className={`w-5 h-px transition-all duration-300 ${
122-
scrolled
122+
scrolled || isOpen
123123
? 'bg-surface-800 dark:bg-white'
124124
: 'bg-white'
125125
} ${isOpen ? 'opacity-0' : ''}`}
126126
/>
127127
<span
128128
className={`w-5 h-px transition-all duration-300 ${
129-
scrolled
129+
scrolled || isOpen
130130
? 'bg-surface-800 dark:bg-white'
131131
: 'bg-white'
132132
} ${isOpen ? '-rotate-45 -translate-y-[3.5px]' : ''}`}
@@ -151,11 +151,7 @@ const Navbar: React.FC<NavbarProps> = ({ theme, toggleTheme }) => {
151151
scrollToSection(item.id);
152152
setIsOpen(false);
153153
}}
154-
className={`block py-3 px-2 text-sm transition-colors ${
155-
scrolled
156-
? 'text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-white'
157-
: 'text-surface-300 hover:text-white'
158-
}`}
154+
className="block py-3 px-2 text-sm transition-colors text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-white"
159155
>
160156
{item.label}
161157
</a>

src/components/Projects.tsx

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -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}&ndash;{Math.min(currentIndex + columns, projects.length)} of {projects.length}
298+
{currentIndex + 1}&ndash;{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

Comments
 (0)