feat(portfolio): 포트폴리오 페이지 전체 구현

- 3탭 구조: 프로필&경력, 프로젝트, 자기소개
- 비밀번호 인증 → 편집 모드
- 클립보드 복사, PDF 내보내기 (window.print)
- 사이버펑크 테마 CSS, 모바일 반응형

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-27 14:37:25 +09:00
parent bebd55874c
commit a6dd2ef747
10 changed files with 1673 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
export default function ResumeView({ data, onClose }) {
const { profile, careers, projects, skills, main_introduction } = data;
const handlePrint = () => {
window.print();
};
return (
<div className="pf-resume-overlay">
<div className="pf-resume-actions no-print">
<button className="button primary" onClick={handlePrint}>PDF 저장 / 인쇄</button>
<button className="button ghost" onClick={onClose}>닫기</button>
</div>
<div className="pf-resume">
{/* 헤더 */}
<header className="pf-resume__header">
<div>
<h1 className="pf-resume__name">{profile.name}</h1>
<p className="pf-resume__role">{profile.role}</p>
</div>
<div className="pf-resume__contact">
{profile.email && <span>{profile.email}</span>}
{profile.phone && <span>{profile.phone}</span>}
{profile.github_url && <span>{profile.github_url}</span>}
</div>
</header>
{/* About */}
{(main_introduction?.content || profile.bio) && (
<section className="pf-resume__section">
<h2>About</h2>
<p>{main_introduction?.content || profile.bio}</p>
</section>
)}
{/* Experience */}
{careers.length > 0 && (
<section className="pf-resume__section">
<h2>Experience</h2>
{careers.map(c => (
<div key={c.id} className="pf-resume__item">
<div className="pf-resume__item-header">
<strong>{c.role}</strong>
<span>{c.organization}</span>
<span className="pf-resume__period">{c.start_date} {c.end_date || '현재'}</span>
</div>
{c.description && <p>{c.description}</p>}
</div>
))}
</section>
)}
{/* Projects */}
{projects.length > 0 && (
<section className="pf-resume__section">
<h2>Projects</h2>
{projects.map(p => (
<div key={p.id} className="pf-resume__item">
<div className="pf-resume__item-header">
<strong>{p.title}</strong>
<span className="pf-resume__period">{p.start_date} {p.end_date || '현재'}</span>
</div>
{p.description && <p>{p.description}</p>}
{p.tech_stack?.length > 0 && (
<p className="pf-resume__tech">{p.tech_stack.join(' · ')}</p>
)}
</div>
))}
</section>
)}
{/* Skills */}
{skills.length > 0 && (
<section className="pf-resume__section">
<h2>Skills</h2>
<p className="pf-resume__skills">{skills.map(s => s.name).join(' · ')}</p>
</section>
)}
</div>
</div>
);
}