From 3d12f48f32928c09dbd345293105f92b60f2102e Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Sat, 5 Sep 2026 17:26:06 +0800 Subject: [PATCH] feat: adapt portfolio sections for smaller screens --- src/components/EducationSection.module.css | 42 +++++- src/components/ExperienceSection.module.css | 51 ++++++- src/components/FooterSection.module.css | 70 ++++++++- src/components/HeroSection.module.css | 40 +++--- src/components/MilestonesSection.module.css | 54 +++++-- src/components/ProjectsSection.module.css | 149 ++++++++++++++------ src/components/ProjectsSection.tsx | 12 +- src/components/SkillsSection.module.css | 106 ++++++++++++-- src/styles/globals.css | 36 ++--- 9 files changed, 440 insertions(+), 120 deletions(-) diff --git a/src/components/EducationSection.module.css b/src/components/EducationSection.module.css index 07449a9..c21cf08 100644 --- a/src/components/EducationSection.module.css +++ b/src/components/EducationSection.module.css @@ -14,11 +14,9 @@ display: flex; align-items: center; gap: 36px; - transition: border-color 0.2s ease; -} - -.card:hover { - border-color: var(--accent-default); + transition: + border-color var(--motion-duration-fast) ease, + transform var(--motion-duration-default) var(--motion-ease-out); } .crestCol { @@ -87,3 +85,37 @@ [data-theme="dark"] .card { background-color: var(--bg-surface); } + +@media (max-width: 760px) { + .educationSection { + padding-top: var(--space-8); + } + + .card { + align-items: flex-start; + flex-direction: column; + gap: var(--space-4); + padding: var(--space-5); + } + + .crestCol, + .crestContainer { + width: 180px; + height: 132px; + } +} + +@media (max-width: 480px) { + .crestCol, + .crestContainer { + width: 144px; + height: 108px; + } +} + +@media (hover: hover) and (pointer: fine) { + .card:hover { + border-color: var(--accent-default); + transform: translateY(-2px); + } +} diff --git a/src/components/ExperienceSection.module.css b/src/components/ExperienceSection.module.css index 653d789..dbeebc7 100644 --- a/src/components/ExperienceSection.module.css +++ b/src/components/ExperienceSection.module.css @@ -34,7 +34,9 @@ background-color: var(--color-slate-400); margin-top: 6px; z-index: 2; - transition: all 0.2s ease; + transition: + background-color var(--motion-duration-fast) ease, + box-shadow var(--motion-duration-default) ease; } .nodeCurrent { @@ -183,3 +185,50 @@ line-height: var(--line-height-body-small); color: var(--text-secondary); } + +@media (max-width: 900px) { + .experienceSection { + padding-top: var(--space-8); + padding-bottom: var(--space-9); + } + + .timelineItem { + min-height: 0; + } + + .itemContent { + flex-direction: column; + gap: var(--space-4); + padding-bottom: var(--space-7); + } + + .identityCol, + .detailsCol { + width: 100%; + max-width: none; + } +} + +@media (max-width: 640px) { + .spineColumn { + width: 24px; + } + + .itemContent { + padding-left: var(--space-3); + } + + .metaRow { + flex-wrap: wrap; + } + + .logoContainer { + width: 60px; + height: 60px; + } + + .roleTitle { + font-size: 18px; + line-height: 26px; + } +} diff --git a/src/components/FooterSection.module.css b/src/components/FooterSection.module.css index 25cea57..f444c82 100644 --- a/src/components/FooterSection.module.css +++ b/src/components/FooterSection.module.css @@ -65,12 +65,9 @@ gap: 12px; color: var(--text-primary); text-decoration: none; - transition: color 0.2s ease, transform 0.2s ease; -} - -.socialItem:hover { - color: var(--accent-default); - transform: translateY(-2px); + transition: + color var(--motion-duration-fast) ease, + transform var(--motion-duration-default) var(--motion-ease-out); } .socialItem:focus-visible { @@ -104,3 +101,64 @@ font-weight: 600; color: var(--text-primary); } + +@media (max-width: 900px) { + .footerSection { + height: auto; + min-height: 480px; + padding: var(--space-10) var(--layout-page-gutter) var(--space-9); + } + + .bgContainer { + height: 100%; + } + + .content { + width: 100%; + margin-top: 0; + } + + .socialRow { + flex-wrap: wrap; + justify-content: center; + gap: var(--space-6) var(--space-8); + } +} + +@media (max-width: 560px) { + .footerSection { + min-height: 560px; + padding-top: var(--space-9); + } + + .ctaTitle { + font-size: 36px; + line-height: 46px; + } + + .ctaSubtitle { + max-width: 300px; + margin-bottom: var(--space-7); + font-size: var(--font-size-body-default); + line-height: var(--line-height-body-default); + letter-spacing: 0.08em; + } + + .socialRow { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + width: 100%; + gap: var(--space-6) var(--space-4); + } + + .socialLabel { + overflow-wrap: anywhere; + } +} + +@media (hover: hover) and (pointer: fine) { + .socialItem:hover { + color: var(--accent-default); + transform: translateY(-2px); + } +} diff --git a/src/components/HeroSection.module.css b/src/components/HeroSection.module.css index c107a4f..e481003 100644 --- a/src/components/HeroSection.module.css +++ b/src/components/HeroSection.module.css @@ -130,22 +130,12 @@ backdrop-filter: none; } -.heroButtonOutline:hover { - border-color: var(--color-blue-700); - background: rgba(255, 255, 255, 0.2); -} - .heroButtonPrimary { border-color: var(--color-blue-600); background: var(--color-blue-600); color: var(--color-blue-white); } -.heroButtonPrimary:hover { - border-color: var(--color-blue-700); - background: var(--color-blue-700); -} - .heroButton:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 3px; @@ -157,22 +147,12 @@ color: var(--text-primary); } -[data-theme="dark"] .heroButtonOutline:hover { - border-color: var(--color-blue-100); - background: rgba(130, 181, 255, 0.12); -} - [data-theme="dark"] .heroButtonPrimary { border-color: var(--color-blue-white); background: var(--color-blue-100); color: var(--color-blue-950); } -[data-theme="dark"] .heroButtonPrimary:hover { - border-color: var(--color-blue-white); - background: var(--color-blue-white); -} - .contactBar { display: inline-flex; align-items: center; @@ -336,4 +316,24 @@ [data-theme="dark"] .iconButton:hover { color: var(--accent-default); } + + .heroButtonOutline:hover { + border-color: var(--color-blue-700); + background: rgba(255, 255, 255, 0.2); + } + + .heroButtonPrimary:hover { + border-color: var(--color-blue-700); + background: var(--color-blue-700); + } + + [data-theme="dark"] .heroButtonOutline:hover { + border-color: var(--color-blue-100); + background: rgba(130, 181, 255, 0.12); + } + + [data-theme="dark"] .heroButtonPrimary:hover { + border-color: var(--color-blue-white); + background: var(--color-blue-white); + } } diff --git a/src/components/MilestonesSection.module.css b/src/components/MilestonesSection.module.css index 842b201..f8dc20a 100644 --- a/src/components/MilestonesSection.module.css +++ b/src/components/MilestonesSection.module.css @@ -19,14 +19,12 @@ padding: 24px 32px; display: flex; gap: 36px; - transition: border-color 0.2s ease; + transition: + border-color var(--motion-duration-fast) ease, + transform var(--motion-duration-default) var(--motion-ease-out); position: relative; } -.certCard:hover { - border-color: var(--accent-default); -} - .thumbnailContainer { position: relative; width: 264px; @@ -43,10 +41,6 @@ transition: transform 0.3s ease; } -.certCard:hover .certImage { - transform: scale(1.02); -} - .certDetails { flex-grow: 1; display: flex; @@ -110,10 +104,44 @@ transition: gap 0.2s ease; } -.viewCertLink:hover { - gap: 10px; -} - [data-theme="dark"] .certCard { background-color: var(--bg-surface); } + +@media (max-width: 760px) { + .milestonesSection { + padding-top: var(--space-8); + padding-bottom: var(--space-9); + } + + .certCard { + flex-direction: column; + gap: var(--space-5); + padding: var(--space-5); + } + + .thumbnailContainer { + width: 100%; + height: auto; + aspect-ratio: 11 / 8; + } + + .actionRow { + justify-content: flex-start; + } +} + +@media (hover: hover) and (pointer: fine) { + .certCard:hover { + border-color: var(--accent-default); + transform: translateY(-2px); + } + + .certCard:hover .certImage { + transform: scale(1.02); + } + + .viewCertLink:hover { + gap: 10px; + } +} diff --git a/src/components/ProjectsSection.module.css b/src/components/ProjectsSection.module.css index 451b317..30e839a 100644 --- a/src/components/ProjectsSection.module.css +++ b/src/components/ProjectsSection.module.css @@ -28,7 +28,11 @@ border: 2px solid var(--color-blue-600); border-radius: 50%; cursor: pointer; - transition: color 0.2s ease, background-color 0.2s ease, transform 0.2s ease; + transition: + color var(--motion-duration-fast) ease, + background-color var(--motion-duration-fast) ease, + border-color var(--motion-duration-fast) ease, + transform var(--motion-duration-fast) var(--motion-ease-out); } .navButtonPrevious { @@ -41,19 +45,6 @@ color: var(--color-blue-white); } -.navButton:hover:not(:disabled) { - transform: translateY(-1px); -} - -.navButtonPrevious:hover:not(:disabled) { - background: var(--color-blue-200); -} - -.navButtonNext:hover:not(:disabled) { - border-color: var(--color-blue-700); - background: var(--color-blue-700); -} - .navButton:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 3px; @@ -68,20 +59,12 @@ color: var(--color-blue-white); } -[data-theme="dark"] .navButtonPrevious:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.12); -} - [data-theme="dark"] .navButtonNext { border-color: var(--color-blue-white); background: var(--color-blue-white); color: var(--color-blue-950); } -[data-theme="dark"] .navButtonNext:hover:not(:disabled) { - background: var(--color-blue-200); -} - .carouselViewport { position: relative; width: 100%; @@ -130,12 +113,6 @@ transition: border-color 0.25s ease, box-shadow 0.25s ease, transform 0.25s ease; } -.card:hover { - border-color: var(--border-strong); - box-shadow: 0 10px 24px var(--shadow-color); - transform: translateY(-2px); -} - .mediaContainer { position: relative; width: 100%; @@ -150,10 +127,6 @@ transition: transform 0.3s ease; } -.card:hover .projectImage { - transform: scale(1.02); -} - .cardBody { display: flex; flex: 1; @@ -243,11 +216,6 @@ transition: color 0.2s ease, gap 0.2s ease; } -.caseStudyLink:hover { - gap: 14px; - color: var(--color-blue-700); -} - .caseStudyLink:focus-visible { border-radius: 2px; outline: 3px solid var(--focus-ring); @@ -258,10 +226,6 @@ color: var(--accent-default); } -[data-theme="dark"] .caseStudyLink:hover { - color: var(--color-blue-100); -} - .scrollbarWrapper { display: flex; justify-content: center; @@ -329,3 +293,106 @@ [data-theme="dark"] .scrollbar::-moz-range-thumb { background: var(--color-blue-300); } + +@media (max-width: 1024px) { + .projectsSection { + padding-top: var(--space-8); + padding-bottom: var(--space-9); + } + + .carouselTrack { + gap: var(--space-4); + } + + .card { + flex-basis: min(340px, calc(100vw - (2 * var(--layout-page-gutter)))); + width: min(340px, calc(100vw - (2 * var(--layout-page-gutter)))); + } +} + +@media (max-width: 640px) { + .headerRow { + align-items: stretch; + flex-direction: column; + gap: var(--space-3); + margin-bottom: var(--space-6); + } + + .arrowButtons { + align-self: flex-end; + gap: var(--space-2); + } + + .navButton { + width: 40px; + height: 40px; + } + + .carouselViewport { + margin-inline: calc(-1 * var(--layout-page-gutter)); + width: calc(100% + (2 * var(--layout-page-gutter))); + padding-inline: var(--layout-page-gutter); + scroll-padding-inline: var(--layout-page-gutter); + scroll-snap-type: x mandatory; + } + + .card { + flex-basis: min(320px, calc(100vw - 40px)); + width: min(320px, calc(100vw - 40px)); + min-height: 460px; + } + + .mediaContainer { + height: 200px; + } + + .cardBody { + padding: var(--space-5); + } + + .scrollbarWrapper { + display: none; + } +} + +@media (hover: hover) and (pointer: fine) { + .navButton:hover:not(:disabled) { + transform: translateY(-1px); + } + + .navButtonPrevious:hover:not(:disabled) { + background: var(--color-blue-200); + } + + .navButtonNext:hover:not(:disabled) { + border-color: var(--color-blue-700); + background: var(--color-blue-700); + } + + .card:hover { + border-color: var(--border-strong); + box-shadow: 0 10px 24px var(--shadow-color); + transform: translateY(-2px); + } + + .card:hover .projectImage { + transform: scale(1.02); + } + + .caseStudyLink:hover { + gap: 14px; + color: var(--color-blue-700); + } + + [data-theme="dark"] .caseStudyLink:hover { + color: var(--color-blue-100); + } + + [data-theme="dark"] .navButtonPrevious:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.12); + } + + [data-theme="dark"] .navButtonNext:hover:not(:disabled) { + background: var(--color-blue-200); + } +} diff --git a/src/components/ProjectsSection.tsx b/src/components/ProjectsSection.tsx index 83e52b0..aaf4e8a 100644 --- a/src/components/ProjectsSection.tsx +++ b/src/components/ProjectsSection.tsx @@ -6,8 +6,6 @@ import { ArrowLeft, ArrowRight, ArrowUpRight } from 'lucide-react'; import { projects } from '@/data/projects'; import styles from './ProjectsSection.module.css'; -const CARD_STEP = 384; - export const ProjectsSection: React.FC = () => { const viewportRef = useRef(null); const dragState = useRef({ @@ -40,8 +38,14 @@ export const ProjectsSection: React.FC = () => { }, []); const scrollByCard = (direction: -1 | 1) => { - viewportRef.current?.scrollBy({ - left: direction * CARD_STEP, + const viewport = viewportRef.current; + const track = viewport?.firstElementChild as HTMLElement | null; + const card = track?.firstElementChild as HTMLElement | null; + if (!viewport || !track || !card) return; + + const gap = Number.parseFloat(window.getComputedStyle(track).columnGap) || 0; + viewport.scrollBy({ + left: direction * (card.getBoundingClientRect().width + gap), behavior: 'smooth', }); }; diff --git a/src/components/SkillsSection.module.css b/src/components/SkillsSection.module.css index a2dfce5..96a5696 100644 --- a/src/components/SkillsSection.module.css +++ b/src/components/SkillsSection.module.css @@ -84,12 +84,9 @@ line-height: var(--line-height-label-small); color: var(--text-secondary); font-weight: 500; - transition: all 0.2s ease; -} - -.badgePill:hover { - border-color: var(--accent-default); - color: var(--text-primary); + transition: + color var(--motion-duration-fast) ease, + border-color var(--motion-duration-fast) ease; } .toolsBlock { @@ -113,16 +110,15 @@ border: 1px solid var(--border-default); border-radius: var(--radius-lg); padding: 16px 8px; - transition: all 0.25s cubic-bezier(0.2, 0, 0.2, 1); + transition: + opacity var(--motion-duration-default) ease, + filter var(--motion-duration-default) ease, + border-color var(--motion-duration-fast) ease, + box-shadow var(--motion-duration-default) ease, + transform var(--motion-duration-default) var(--motion-ease-out); cursor: default; } -.toolTile:hover { - border-color: var(--accent-default); - transform: translateY(-4px); - box-shadow: 0 8px 24px var(--shadow-color); -} - .toolDimmed { opacity: 0.35; filter: grayscale(0.8); @@ -168,3 +164,87 @@ [data-theme="dark"] .toolTile { background-color: var(--bg-raised); } + +@media (max-width: 1180px) { + .toolsGrid { + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: var(--space-4); + } + + .toolTile { + width: 100%; + } +} + +@media (max-width: 900px) { + .skillsSection { + padding-top: var(--space-8); + padding-bottom: var(--space-9); + } + + .mainCard { + padding: var(--space-6); + } + + .skillsGrid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + column-gap: var(--space-6); + } + + .toolsGrid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .filterTabsRow { + flex-wrap: wrap; + } +} + +@media (max-width: 560px) { + .mainCard { + padding: var(--space-5); + } + + .skillsGrid { + grid-template-columns: 1fr; + row-gap: var(--space-5); + } + + .toolsGrid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: var(--space-3); + } + + .toolTile { + height: 124px; + padding: var(--space-3) var(--space-2); + } + + .toolIconContainer { + width: 52px; + height: 52px; + } + + .filterTabsRow { + align-items: stretch; + flex-direction: column; + gap: var(--space-2); + } + + .filterButton { + width: 100%; + } +} + +@media (hover: hover) and (pointer: fine) { + .badgePill:hover { + border-color: var(--accent-default); + color: var(--text-primary); + } + + .toolTile:hover { + border-color: var(--accent-default); + box-shadow: 0 8px 24px var(--shadow-color); + transform: translateY(-3px); + } +} diff --git a/src/styles/globals.css b/src/styles/globals.css index 599c8d1..e445d99 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -158,35 +158,18 @@ body { color: var(--text-primary); } -.pill-btn-outline:hover { - background: rgba(255, 255, 255, 0.7); - border-color: var(--accent-default); - transform: translateY(-1px); -} - [data-theme="dark"] .pill-btn-outline { background: rgba(9, 28, 63, 0.6); border-color: var(--border-default); color: var(--text-primary); } -[data-theme="dark"] .pill-btn-outline:hover { - background: rgba(16, 63, 127, 0.8); - border-color: var(--accent-default); -} - .pill-btn-primary { background-color: var(--action-primary-bg); color: var(--action-primary-fg); border-color: transparent; } -.pill-btn-primary:hover { - background-color: var(--action-primary-hover); - transform: translateY(-1px); - box-shadow: 0 4px 12px var(--shadow-color); -} - @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; @@ -204,3 +187,22 @@ a { color: inherit; text-decoration: none; } + +@media (hover: hover) and (pointer: fine) { + .pill-btn-outline:hover { + border-color: var(--accent-default); + background: rgba(255, 255, 255, 0.7); + transform: translateY(-1px); + } + + [data-theme="dark"] .pill-btn-outline:hover { + border-color: var(--accent-default); + background: rgba(16, 63, 127, 0.8); + } + + .pill-btn-primary:hover { + background-color: var(--action-primary-hover); + box-shadow: 0 4px 12px var(--shadow-color); + transform: translateY(-1px); + } +}