
interface ProjectCardProps { project: Project; // 조건 1: 'project'라는 항목이 반드시 있어야 한다. onClick: () => void; // 조건 2: 'onClick'이라는 항목이 반드시 있어야 한다. }
project: Project;
=> 즉, 전달받는 project 데이터의 타입은 src/types/index.ts에서 정의한 Project 설계도(id, title, videoSrc 등이 있는)를 반드시 따라야 한다는 뜻이다.
onClick: () => void;
: 모달을 띄우는 실제 기능은 ProjectCard가 아니라, 그 부모의 부모인 page.tsx에 있는 handleProjectClick 함수가 가지고 있다.
=> 즉, ProjectCard는 사용자가 자신을 클릭했다는 사실을 부모에게 알려야 한다.
=> 이 onClick prop은 부모(page.tsx -> Projects -> ProjectCard)가 자식에게 "만약 네가 클릭되면, 내가 전달해준 이 함수를 대신 실행해 줘!"라고 말하며 넘겨주는 것이다.


이 <div>는 왼쪽 카드(정보)와 오른쪽 카드(설명)를 어떻게 배치할지 결정한다.
grid :<div>를 CSS Grid 컨테이너로 만든다.
=> 이 <div>의 직계 자식들(왼쪽 카드 div와 오른쪽 카드 div)을 격자판 위에 올린다.
grid-cols-1: (모바일 기본) 세로 1줄짜리이다.
=> 모바일 화면에서는 왼쪽 카드와 오른쪽 카드가 순서대로 수직으로 쌓인다.
lg:grid-cols-5: (반응형) 격자판을 5개의 세로 열로 만들라는 뜻이다.
=> 결과: 데스크톱 화면(1024px 이상)에서는 가로 5칸짜리 격자판으로 변신합니다.
(이 5칸을 자식인 왼쪽 카드가 lg:col-span-2(2칸) 차지하고, 오른쪽 카드가 lg:col-span-3(3칸) 차지하여 2 + 3 = 5칸 레이아웃이 완성된다.)
gap-8: 격자판의 사이의 여백을 32px로 설정한다.
=> 모바일에서는 위아래 카드의 세로 간격, 데스크톱에서는 좌우 카드의 가로 간격이 된다.
items-center: 그리드 아이템(자식인 왼쪽/오른쪽 카드)들을 각자 배정된 칸 안에서 세로 방향으로 중앙에 배치한다.
=> (데스크톱에서 왼쪽 카드의 높이와 오른쪽 카드의 높이가 다르더라도, 두 카드가 서로 세로 중앙에 보기 좋게 정렬된다.)

lg:col-span-2: 1024px 이상의 화면에서, 5칸 중 2칸을 차지하겠다는 뜻이다.
group: 자식 요소의 스타일을 부모의 hover 상태에 따라 제어하게 해준다.
=> 이 <div>에 group 클래스를 붙여놓았기 때문에, 이 <div>에 마우스를 올리면 자식 div의 group-hover:blur-sm, group-hover:scale-[1.02] 등의 클래스가 작동한다.
relative: 이 <div> 자체에는 아무 변화도 없지만, 자식 요소가 position: absolute를 사용할 때의 기준점이 된다.
=> 아래 코드에 있는 자식 코드인 '호버 오버레이'는 absolute inset-0 (부모의 4방향에 꽉 참) 속성을 가진다. 이 absolute는 relative가 설정된 가장 가까운 부모를 기준으로 위치를 잡는다.
=> 즉, relative는 검은색 오버레이가 이 왼쪽 카드에 정확히 겹쳐지도록 만드는 역할을 한다.
overflow-hidden: 이 <div>의 테두리를 벗어나는 모든 자식 요소를 숨긴다.
=> group-hover:scale-[1.02] (살짝 커짐) 효과 시 overflow-hidden이 없으면, 커진 div가 둥근 모서리 밖으로 삐져나가게 된다.
rounded-xl: 이 <div>의 모서리를 0.75rem (12px)만큼 둥글게 깎는다.
cursor-pointer: 마우스를 올리면, 커서가 기본 화살표에서 손가락 모양으로 바뀐다.
onClick={onClick}: 만약 사용자가 이 <div>를 클릭하면, props로 전달받은 onClick 함수를 실행시킨다.
card-bg: globals.css에 직접 커스텀한 클래스이다.
bg-[rgba(17,24,39,0.6)]: 반투명한 어두운 배경색을 직접 지정한다.
p-6: 상하좌우에 1.5rem (24px)의 안쪽 여백을 준다.
rounded-lg: 카드의 모서리를 0.5rem (8px)만큼 둥글게 깎는다.
group-hover:blur-sm: 부모 <div>에 마우스를 올리면, 이 <div>에 약한 흐림 효과를 적용한다.
group-hover:scale-[1.02]: 부모 <div>에 마우스를 올리면, 이 <div>를 1.02배(2%) 확대시킨다.
transition-all: 이 <div>의 모든 속성(흐림, 확대 등)이 변할 때 애니메이션을 적용한다.
duration-300: 위 애니메이션이 0.3초 동안 부드럽게 일어나도록 한다.
h-full: 이 카드의 높이를 부모 <div>의 높이만큼 채운다.
text-2xl: 제목 글자의 크기를 1.5rem(24px)로 설정한다.
font-bold: weight 값을 700으로 설정하여 제목을 굵게 만든다.
text-white: 글자 색상을 흰색으로 지정한다.
mb-2: 아래쪽에 0.5rem (8px) 여백을 준다.
{project.title}: 부모 컴포넌트로부터 props로 전달받은 project 객체에서, title 속성값을 꺼내와 이 자리에 텍스트로 표시한다.
=> {...}: JSX 안에서 JavaScript 변수나 표현식을 사용하겠다는 기호이다.
text-base: 부모의 text-2xl (24px)를 덮어쓰고, 글자 크기를 1rem (16px)로 설정한다.
font-medium: 부모의 font-bold(700)를 덮어쓰고, font-weight: 500으로 설정하여 덜 굵은 굵기로 만든다.
text-gray-400: 글자 색상을 회색으로 설정한다.
ml-2: margin-left: 0.5rem (8px)을 설정한다.
=> 앞의 {project.title} 텍스트와 이 <span> 텍스트 사이에 8px의 왼쪽 여백을 준다.
{project.isTeamProject ? "(팀 프로젝트)" : "(개인 프로젝트)"}
=> JavaScript의 삼항 연산자이다.
=> 만약 project.isTeamProject 값이 true라면 (팀 프로젝트) 텍스트를 보여준다.
=> 그렇지 않고 false라면 (개인 프로젝트) 텍스트를 보여준다."
이 코드는 <h3> 바로 아래에 오는 부제목 텍스트이다.
text-indigo-300: 텍스트의 색상을 밝은 남색(indigo) 계열로 설정한다.
font-semibold: 텍스트의 굵기를 세미볼드로 설정한다.
mb-4: 아래에 1rem (16px)의 여백을 준다.
{project.subtitle}: 부모 컴포넌트로부터 props로 전달받은 project 객체에서, subtitle 속성값을 꺼내와 이 자리에 텍스트로 표시한다.
이 코드는 "기간: 프로젝트 기간"을 표시한다.
mb-2: 아래에 0.5rem (8px)의 여백을 준다.
text-gray-300: 글자 색상을 밝은 회색으로 설정한다.
<strong> 태그: HTML 시맨틱 태그로, 텍스트가 중요한 라벨임을 의미한다.
font-semibold: 텍스트의 굵기를 세미볼드로 설정한다.
text-gray-300: 글자 색상을 밝은 회색으로 설정한다.
=> <strong> 태그가 부모(p)의 색상을 상속받지만, 굵기가 달라지면서 색상이 미세하게 달라 보일 수 있으므로, p 태그와 동일한 gray-300 색상을 명시적으로 다시 지정해준다.
{project.period}: 부모 컴포넌트로부터 props로 전달받은 project 객체에서, period 속성값을 꺼내와 이 자리에 텍스트로 표시합니다.
mt-4: 위쪽에 1rem (16px)의 여백을 준다.
font-bold: 텍스트를 굵게 만든다.
text-gray-300: 글자 색상을 밝은 회색으로 설정한다.
mb-4: 아래쪽에 1rem (16px)의 여백을 준다.
<ul>: 순서가 없는 목록의 약자이다. (목록 앞에 불릿(•)이 붙는다.)
list-disc: 목록의 불릿 스타일을 채워진 원(•) (disc)으로 설정한다.
list-inside: 불릿(•)을 <li> 태그의 안쪽에 배치하여 텍스트와 함께 정렬되도록 한다.
text-gray-400: 이 목록 안의 모든 <li> 텍스트 색상을 회색으로 설정한다.
space-y-1: 자식 요소(<li>)들 사이에 세로 간격을 0.25rem (4px)씩 준다.
{project.role.map((role, index) => ...)}
=> {...}: JSX 안에서 JavaScript 코드를 실행합니다.
=> .map((role, index) => ...): JavaScript의 .map() 배열 함수이다.
=> 이 role 배열을 처음부터 끝까지 순회하면서, 각 항목마다 => 뒤의 코드를 실행해 줘.
=> index: 배열의 순서(0, 1, 2...)가 이 변수에 담긴다.
<li key={index}>{role}</li>: .map() 함수가 반환하는 결과물이다.
=> role 배열의 항목 수만큼 <li> 태그가 생성된다.
=> {role}: <li> 태그 안에 role 변수에 담긴 텍스트를 표시합니다.
=> key={index}: (React 규칙) .map()을 사용해 목록을 만들 때는, React가 각 항목을 빠르고 정확하게 구분할 수 있도록 고유한 식별자(key)를 반드시 제공해야 한다.

absolute: 부모를 기준으로 공중에 띄운다.
inset-0: top: 0; right: 0; bottom: 0; left: 0;를 한 번에 쓴 것이다.
=> 공중에 뜬 상태로, 부모의 상하좌우 4면에 꽉 차게 늘어난다.
bg-black: 배경색을 검은색으로 한다.
bg-opacity-60: 검은색 배경의 불투명도를 60%로 설정한다.
p-4: 안쪽에 16px의 여백을 준다. (아이콘과 텍스트가 가장자리에 붙지 않게 한다.)
flex: 이 div를 flex 컨테이너로 만든다.
flex-col: 자식 요소(아이콘, 텍스트)를 세로로 쌓는다.
justify-center: flex-col 상태이므로, 자식 요소들을 '세로축'의 정중앙에 배치한다.
items-center: flex-col 상태이므로, 자식 요소들을 '가로축'의 정중앙에 배치한다.
text-center: p 태그의 텍스트 자체를 가운데 정렬한다.
=> items-center가 가로 중앙 정렬인데, text-center는 왜 또 필요할까?
=> items-center는 <p> 태그 자체(상자)를 가로 중앙으로 옮긴다.
=> text-center는 그 <p> 태그 안에 있는 글자를 가운데 정렬한다.
opacity-0: (기본 상태) opacity: 0을 의미한다.
=> 평소에는 이 div 전체를 100% 투명하게 만들어 숨긴다.
group-hover:opacity-100: (호버 상태) 이 div의 opacity를 100%로 만든다.
transition-opacity: 오직 opacity 속성에만 애니메이션을 적용한다.
duration-300: opacity가 0에서 100으로 변할 때, 0.3초 동안 부드럽게 일어나도록 한다.
className="...": PlusIcon 컴포넌트에 prop을 전달한다.
여기서 전달한 className 값은 PlusIcon.tsx 파일 내부의 <svg className={className} ...> 코드에 의해, SVG 태그의 class 속성으로 그대로 적용됩니다.
h-10: 아이콘의 높이를 2.5rem (40px)로 설정한다.
w-10: 아이콘의 너비를 2.5rem (40px)로 설정한다.
text-white: SVG 아이콘은 텍스트처럼 취급될 수 있어서, text-white로 아이콘의 색상을 흰색으로 설정한다.
mb-4: 아래에 1rem (16px) 여백을 준다.

lg:col-span-3: 스크린이 1024px 이상일 때 5칸 중 3칸을 차지한다.
card-bg: globals.css에 직접 만든 커스텀 클래스를 적용한다.
bg-[rgba(17,24,39,0.6)]: bg- 접두사를 사용해 이 div의 background-color를 rgba(17, 24, 39, 0.6) (반투명한 어두운 색)로 직접 지정한다.
p-6: 상하좌우에 1.5rem(24px)의 안쪽 여백을 준다.
rounded-lg: 카드의 모서리를 0.5rem(8px)만큼 둥글게 깎는다.
mb-4: 아래에 1rem (16px)의 여백을 준다.
text-gray-300: 텍스트의 색상을 밝은 회색으로 설정한다.
{project.description}: 부모 컴포넌트로부터 props로 전달받은 project 객체에서, description 속성값을 꺼내와 이 자리에 텍스트로 표시한다.
font-semibold: 텍스트의 굵기를 세미볼드로 설정한다.
text-gray-200: 텍스트의 색상을 더 밝은 회색으로 설정한다.
mb-2: 아래에 0.5rem (8px)의 여백을 준다.
flex: display: flex. 자식 요소들을 가로로 정렬한다.
flex-wrap: 만약 기술 스택 태그가 너무 많아서 한 줄에 다 안 들어가면, 줄바꿈을 허용하여 다음 줄로 넘어가게 한다.
gap-2: flex의 자식 요소들 사이에 0.5rem(98px)의 가로/세로 간격을 준다.
{project.techStack.map((tech) => ...)}: <span> ... </span> 태그를 배열의 항목 수만큼 생성하여 <div> 안에 넣어준다.
key={tech}: .map()을 사용할 때 React가 각 항목을 구분하기 위해 필요한 고유 ID이다.
className="...": 이 className은 2부분으로 나뉜다.
text-white: 태그 안의 텍스트 색상을 흰색으로 설정한다.
text-sm: 폰트 크기를 0.875rem (14px)로 설정한다.
font-medium: font-weight: 500. 텍스트의 굵기를 미디엄으로 설정한다.
px-3: 텍스트의 좌우에 12px의 안쪽 여백을 준다.
py-1: 텍스트의 위아래에 4px의 안쪽 여백을 줍니다.
rounded-full: 태그의 모서리를 완전한 원형(타원형)으로 깎는다.
${techColorMap[tech] || "bg-gray-500/50"}:
=> techColorMap[tech]: src/data/projects.ts에서 가져온 techColorMap 객체에서, 현재 tech 변수를 Key로 사용해 Value를 찾는다.
=> || "bg-gray-500/50": ||는 OR(또는)을 의미한다. 만약 techColorMap에서 tech 이름(Key)을 못 찾으면, 기본값으로 회색(bg-gray-500/50)을 사용하라는 뜻이다.
