Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 79 additions & 55 deletions src/components/ResumePdf.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,67 +31,81 @@ const colors = {
primary: "#18181b",
secondary: "#3f3f46",
muted: "#71717a",
faint: "#a1a1aa",
accent: "#2563eb",
border: "#e4e4e7",
bgLight: "#f4f4f5",
}

const s = StyleSheet.create({
page: {
fontFamily: "Pretendard",
fontSize: 9,
color: colors.primary,
paddingTop: 36,
paddingBottom: 36,
paddingHorizontal: 40,
paddingTop: 42,
paddingBottom: 42,
paddingHorizontal: 42,
lineHeight: 1.5,
},
// Header
header: { marginBottom: 20 },
name: { fontSize: 22, fontWeight: 700, lineHeight: 1.2, marginBottom: 6 },
role: { fontSize: 11, color: colors.muted, marginBottom: 8 },
contactRow: { flexDirection: "row", gap: 12 },
contactLink: { fontSize: 8.5, color: colors.accent, textDecoration: "none" },
intro: { fontSize: 9, color: colors.secondary, lineHeight: 1.6, marginTop: 12 },
header: { marginBottom: 24 },
name: { fontSize: 23, fontWeight: 600, letterSpacing: -0.3, lineHeight: 1.25, marginBottom: 9 },
role: { fontSize: 8.5, fontWeight: 500, letterSpacing: 1.8, textTransform: "uppercase", color: colors.muted, marginBottom: 13 },
contactRow: { flexDirection: "row", gap: 8 },
contactLink: { fontSize: 8.5, color: colors.secondary, textDecoration: "none" },
contactText: { fontSize: 8.5, color: colors.secondary },
contactSep: { fontSize: 8.5, color: colors.faint },
intro: { fontSize: 9, color: colors.secondary, lineHeight: 1.7, marginTop: 15 },
// Section
sectionTitle: {
fontSize: 12,
fontWeight: 700,
color: colors.primary,
marginBottom: 8,
marginTop: 16,
paddingBottom: 4,
borderBottomWidth: 1.5,
borderBottomColor: colors.primary,
fontSize: 10.5,
fontWeight: 600,
letterSpacing: 1.8,
textTransform: "uppercase",
color: colors.muted,
marginTop: 26,
marginBottom: 14,
paddingBottom: 6,
borderBottomWidth: 1,
borderBottomColor: colors.border,
},
// Skills
skillRow: { flexDirection: "row", marginBottom: 4 },
skillCategory: { width: 80, fontSize: 8.5, fontWeight: 600, color: colors.secondary },
skillList: { flex: 1, fontSize: 8.5, color: colors.muted },
skillRow: { flexDirection: "row", marginBottom: 6 },
skillCategory: { width: 92, fontSize: 8.5, fontWeight: 600, color: colors.primary },
skillList: { flex: 1, fontSize: 8.5, color: colors.secondary },
// Company
companyHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 2, marginTop: 10 },
companyName: { fontSize: 11, fontWeight: 700 },
companyOuterFirst: { marginTop: 6 },
companyOuter: { marginTop: 18 },
companyIntro: {},
companyIntroDivided: { paddingTop: 16, borderTopWidth: 1, borderTopColor: colors.border },
companyHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "baseline", marginBottom: 3 },
companyName: { fontSize: 13, fontWeight: 700, letterSpacing: -0.2 },
companyPeriod: { fontSize: 8.5, color: colors.muted },
companyRole: { fontSize: 9, color: colors.muted, marginBottom: 6 },
companyRole: { fontSize: 9, color: colors.muted, marginBottom: 12 },
// Project
projectBox: { marginBottom: 14, paddingLeft: 8, borderLeftWidth: 2, borderLeftColor: colors.border },
projectHeader: { flexDirection: "row", justifyContent: "space-between", marginBottom: 4 },
projectName: { fontSize: 9.5, fontWeight: 600, marginBottom: 2 },
projectPeriod: { fontSize: 8, color: colors.muted },
techRow: { flexDirection: "row", flexWrap: "wrap", gap: 4, marginBottom: 6 },
techBadge: { fontSize: 7.5, color: colors.accent, backgroundColor: "#eff6ff", paddingHorizontal: 5, paddingVertical: 1.5, borderRadius: 3 },
projectBox: { marginBottom: 14 },
projectName: { fontSize: 10, fontWeight: 600, color: colors.primary, marginBottom: 3 },
techInline: { fontSize: 8, color: colors.muted, letterSpacing: 0.2, marginBottom: 8 },
// Task
taskGroup: { marginBottom: 5 },
taskItem: { fontSize: 8.5, color: colors.secondary, marginBottom: 3, paddingLeft: 8, lineHeight: 1.5 },
detailItem: { fontSize: 8, color: colors.muted, marginBottom: 3, paddingLeft: 18, lineHeight: 1.5 },
taskRow: { flexDirection: "row", paddingLeft: 2 },
taskBullet: { width: 9, fontSize: 8.5, color: colors.faint, lineHeight: 1.6 },
taskItem: { flex: 1, fontSize: 8.5, color: colors.secondary, lineHeight: 1.6 },
detailRow: { flexDirection: "row", paddingLeft: 16, marginTop: 2 },
detailArrow: { width: 12, fontSize: 8, color: colors.faint, lineHeight: 1.6 },
detailItem: { flex: 1, fontSize: 8, color: colors.muted, lineHeight: 1.6 },
// Achievement (small blue dot marker + emphasized metric)
achGroup: { marginTop: 7 },
achRow: { flexDirection: "row", marginBottom: 4, paddingLeft: 2 },
achDot: { width: 3, height: 3, borderRadius: 1.5, backgroundColor: colors.accent, marginTop: 4, marginRight: 8 },
achText: { flex: 1, fontSize: 8.5, color: colors.primary, lineHeight: 1.55 },
// Certifications
certRow: { flexDirection: "row", justifyContent: "space-between", marginBottom: 3 },
certRow: { flexDirection: "row", justifyContent: "space-between", marginBottom: 5 },
certName: { fontSize: 9, color: colors.secondary },
certYear: { fontSize: 8.5, color: colors.muted },
boldText: { fontWeight: 700 },
contactText: { fontSize: 8.5, color: colors.muted },
// URLs
urlRow: { flexDirection: "row", marginBottom: 4 },
urlLabel: { width: 110, fontSize: 8.5, fontWeight: 600, color: colors.secondary },
urlRow: { flexDirection: "row", marginBottom: 5 },
urlLabel: { width: 110, fontSize: 8.5, fontWeight: 600, color: colors.primary },
})

/** Parse **bold** markdown into Text elements */
Expand All @@ -110,42 +124,51 @@ function BoldText({ style, children }: { style: Styles[string]; children: string
)
}

function CompanySection({ company }: { company: Company }) {
function CompanySection({ company, isFirst }: { company: Company; isFirst: boolean }) {
const companyProjects = company.projects
.map((ps) => PROJECTS.find((p) => p.slug === ps.slug))
.filter(Boolean)

return (
<View>
<View style={s.companyHeader}>
<Text style={s.companyName}>{company.name}</Text>
<Text style={s.companyPeriod}>{company.period}</Text>
<View style={isFirst ? s.companyOuterFirst : s.companyOuter}>
<View wrap={false} minPresenceAhead={80} style={isFirst ? s.companyIntro : s.companyIntroDivided}>
<View style={s.companyHeader}>
<Text style={s.companyName}>{company.name}</Text>
<Text style={s.companyPeriod}>{company.period}</Text>
</View>
<Text style={s.companyRole}>{company.role}</Text>
</View>
<Text style={s.companyRole}>{company.role}</Text>

{companyProjects.map((project) => (
<View key={project!.slug} style={s.projectBox}>
<View wrap={false} minPresenceAhead={50}>
<Text style={s.projectName}>{project!.name}</Text>
<View style={s.techRow}>
{project!.tech.map((t) => (
<Text key={t} style={s.techBadge}>{t}</Text>
))}
</View>
<Text style={s.techInline}>{project!.tech.join(" · ")}</Text>
</View>
{project!.tasks.map((task, i) => (
<View key={i} style={s.taskGroup} wrap={false}>
<BoldText style={s.taskItem}>{"- " + task.content}</BoldText>
<View style={s.taskRow}>
<Text style={s.taskBullet}>·</Text>
<BoldText style={s.taskItem}>{task.content}</BoldText>
</View>
{task.details?.map((detail, j) => (
<BoldText key={j} style={s.detailItem}>{"→ " + detail}</BoldText>
<View key={j} style={s.detailRow}>
<Text style={s.detailArrow}>→</Text>
<BoldText style={s.detailItem}>{detail}</BoldText>
</View>
))}
</View>
))}
{project!.achievements?.map((ach, i) => (
<View key={`ach-${i}`} style={s.taskGroup} wrap={false}>
<BoldText style={s.taskItem}>{"- " + ach}</BoldText>
{project!.achievements && project!.achievements.length > 0 && (
<View style={s.achGroup}>
{project!.achievements.map((ach, i) => (
<View key={`ach-${i}`} style={s.achRow} wrap={false}>
<View style={s.achDot} />
<BoldText style={s.achText}>{ach}</BoldText>
</View>
))}
</View>
))}
)}
</View>
))}
</View>
Expand All @@ -162,15 +185,16 @@ export function ResumePdfDocument() {
<Text style={s.role}>Backend Engineer</Text>
<View style={s.contactRow}>
<Link src={`mailto:${PROFILE.email}`} style={s.contactLink}>{PROFILE.email}</Link>
<Text style={s.contactSep}>·</Text>
<Text style={s.contactText}>{PROFILE.phone}</Text>
</View>
<Text style={s.intro}>{PROFILE.introduction}</Text>
</View>

{/* Experience */}
<Text style={s.sectionTitle}>Experience</Text>
{COMPANIES.map((company) => (
<CompanySection key={company.name} company={company} />
{COMPANIES.map((company, idx) => (
<CompanySection key={company.name} company={company} isFirst={idx === 0} />
))}

{/* Skills */}
Expand Down
Loading