diff --git a/README.md b/README.md index e243543..92b609e 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,13 @@ Personal portfolio website featuring dual personas (Creative & IT), inspired by - **Typography**: Syne (display) + IBM Plex Mono - **Default theme**: Light -### Print CV -- Separate print-optimized A4 format -- Concise professional journey +### Print CV (Downloadable PDF) +- Professional B&W print-friendly A4 format +- Two-column layout: Sidebar (contact, skills, education) + Main content +- Includes all 8 job experiences from Graphic Artist to AI Creative Lead +- Strategic IT Projects section showcasing Full-Stack development skills +- Awards & Recognition section +- Clean, minimal design optimized for HR/recruiters ## Tech Stack @@ -53,6 +57,7 @@ npm run build ## Print CV Press the "Download CV" button or use `window.print()` to generate the PDF CV. +Select "Save as PDF" in the print dialog. ## License diff --git a/src/App.jsx b/src/App.jsx index f995f31..ac4675c 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1384,7 +1384,7 @@ export default function Portfolio() { )} - {/* Permanently mount the print portfolio so the browser can natively invoke it */} + {/* Print-only portfolio - hidden on screen */}
diff --git a/src/PrintPortfolio.jsx b/src/PrintPortfolio.jsx index c7fc059..66c4223 100644 --- a/src/PrintPortfolio.jsx +++ b/src/PrintPortfolio.jsx @@ -2,14 +2,14 @@ import React from 'react'; const PRINT_PERSONAL_INFO = { name: "Khoa.vo", - title: "CREATIVE MANAGER & AI INNOVATION LEAD", + title: "CREATIVE MANAGER & AI-POWERED DEVELOPER", location: "Ho Chi Minh City, Vietnam", phone: "0398300340", email: "vonguyendangkhoa@gmail.com", - linkedin: "linkedin.com/in/khoavo", + linkedin: "linkedin.com/in/khoavo93", portfolio: "khoavo.myds.me", - summaryHeadline: "Visionary Creative Leader merging Brand Strategy with Generative AI.", - summaryBody: "With 9+ years of expertise at global firms like P&G and Phibious, I bridge the gap between artistic direction and high-performance automation. I specialize in designing scalable AI video production workflows and agentic systems that serve Fortune 500 brands while driving measurable growth and operational excellence.\n\nBeyond traditional creative direction, my recent evolution into an AI-Powered Developer enables me to architect custom web applications and full-stack deployment pipelines (React, Go, Docker). By unifying deep brand-building experience with hands-on coding and machine learning integration, I transform creative conceptualization into quantifiable, automated, and highly scalable digital realities." + summaryHeadline: "Creative Leader | AI Innovation | Full-Stack Developer", + summaryBody: "With 9+ years at P&G and Phibious, I bridge creative direction with AI automation—building scalable video production workflows for Fortune 500 brands.\n\nAs an AI-Powered Developer, I also build production apps: video streaming platforms (Go, Docker), AI image generators, and Android TV apps." }; const PRINT_EDUCATION = [ @@ -42,7 +42,7 @@ const PRINT_EXPERIENCES = [ }, { role: "eCOM Design Lead", - company: "Procter & Gamble", + company: "Procter & Gamble (P&G)", period: "Sep 2023 - Jun 2025", highlights: [ "Directed visual strategies for P&G's Hair Care portfolio across SEA, impacting millions of consumers effectively.", @@ -62,24 +62,81 @@ const PRINT_EXPERIENCES = [ }, { role: "Production Creative Lead", - company: "Inn Saigon", + company: "INN SaiGon", period: "Dec 2019 - Nov 2020", highlights: [ - "Directed a multi-disciplinary team of photographers and retouchers for high-profile hospitality clients.", + "Directed photography production for food, product, and event projects with 30+ client accounts across hospitality, F&B, and luxury retail sectors.", "Managed production budgets and resource allocation for 30+ simultaneous client accounts.", "Implemented quality control frameworks that reduced post-production errors by 40%." ] + }, + { + role: "Regional Head of Design", + company: "ASIAMARINE", + period: "2018 - 2019", + highlights: [ + "Led design team creating digital marketing assets, web graphics, and editorial content for Vietnam's premier luxury yacht brand.", + "Delegated projects to junior designers while maintaining quality control and brand consistency across all touchpoints.", + "Developed the visual identity system that defined ASIAMARINE's premium positioning in the regional marine lifestyle sector." + ] + }, + { + role: "Senior Graphic Designer", + company: "EMG - Element Management Group", + period: "2017 - 2018", + highlights: [ + "Created impactful designs for print and digital campaigns for global luxury and lifestyle brands.", + "Expert in photo sourcing, advanced image retouching, and brand identity development.", + "Developed production-ready artwork for offset and digital print, ensuring color accuracy across media." + ] + }, + { + role: "Graphic Artist", + company: "Le Meridien Saigon", + period: "2016 - 2017", + highlights: [ + "Created visual materials for hotel marketing campaigns and guest communications.", + "Designed menus, promotional materials, and digital signage for the hotel.", + "Collaborated with marketing team to maintain brand standards across all touchpoints." + ] + }, + { + role: "Animation Designer", + company: "Adidas Sourcing", + period: "2015 - 2016", + highlights: [ + "Designed animations for product showcases and marketing presentations.", + "Created motion graphics for internal and external communications.", + "Worked with design team to develop visual content for Adidas products." + ] } ]; const PRINT_STRATEGIC_TECH = [ - { name: "KV-Tube", tech: "Go, Next.js, Docker", desc: "Enterprise-grade HLS video streaming platform with custom NAS deployment architecture." }, - { name: "APIx GenAI", tech: "TypeScript, LLM APIs", desc: "A custom AI image generation portal integrating multiple LLM and Diffusion providers." } + { name: "KV-Tube", tech: "Go, Next.js, Docker, HLS", desc: "Enterprise-grade HLS video streaming platform deployed on Synology NAS. Features: user management, watch history, playlists, PWA support." }, + { name: "APIx GenAI", tech: "TypeScript, Multi-LLM APIs", desc: "AI image generation portal integrating Google Whisk, Meta AI, Grok. Multi-provider support with prompt library and history." }, + { name: "KV-Netflix", tech: "Kotlin, Compose Multiplatform", desc: "Full-featured Netflix clone for Android TV + Web. Movie streaming with trailer preview, custom playlists, offline downloads." }, + { name: "Spotify Clone", tech: "React, Rust (Axum), YouTube API", desc: "Music player with YouTube Music integration, real-time lyrics, custom playlists, PWA support." } ]; -// --- BRANDING COMPONENT (B&W Friendly) --- -const PRINT_VNDKLogo = ({ size = 100, vnColor = "#000000", dkColor = "#333333" }) => ( - +const PRINT_COLORS = { + primary: '#000000', + secondary: '#333333', + tertiary: '#666666', + accent: '#000000', + black: '#000000', + white: '#FFFFFF', + grey: '#444444', + muted: '#666666', + lightBg: '#f5f5f5', + border: '#cccccc', + sidebarBg: '#ffffff', + sidebarBorder: '#000000', + gradient: 'none' +}; + +const VndkLogo = ({ size = 60, vnColor = "#000000", dkColor = PRINT_COLORS.black }) => ( + @@ -87,221 +144,259 @@ const PRINT_VNDKLogo = ({ size = 100, vnColor = "#000000", dkColor = "#333333" } ); -const PRINT_COLORS = { - accent: '#222222', // Replaced mint with dark grey for B&W printing - black: '#000000', - white: '#FFFFFF', - grey: '#444444', - muted: '#666666', - border: '#DDDDDD' -}; - const PRINT_STYLES = { container: { width: '100%', maxWidth: '210mm', + minHeight: '297mm', background: PRINT_COLORS.white, - fontFamily: "'Inter', sans-serif", - color: PRINT_COLORS.black, + fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif", + color: PRINT_COLORS.grey, margin: '0 auto', boxSizing: 'border-box', display: 'flex', - flexDirection: 'column', position: 'relative', - zIndex: 1, }, - documentContainer: { - padding: '0', - backgroundColor: PRINT_COLORS.white, +sidebar: { + width: '75mm', + minHeight: '297mm', + background: PRINT_COLORS.white, + borderRight: `2px solid ${PRINT_COLORS.black}`, + padding: '10mm 8mm', display: 'flex', flexDirection: 'column', + gap: '10mm', + position: 'relative', }, - headerWrapper: { - backgroundColor: PRINT_COLORS.white, - padding: '16mm 18mm 12mm 18mm', - width: '100%', - boxSizing: 'border-box', - borderTop: `6mm solid ${PRINT_COLORS.black}`, - borderBottom: `1px solid ${PRINT_COLORS.border}`, - marginBottom: '10mm' - }, - topHeader: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - marginBottom: '10mm', - }, - logoSlot: { - border: `2px solid ${PRINT_COLORS.black}`, - padding: '2mm', + profileImage: { + width: '30mm', + height: '30mm', + margin: '0 auto 5mm', display: 'flex', alignItems: 'center', justifyContent: 'center', - backgroundColor: PRINT_COLORS.white, - }, - nameTitle: { - display: 'flex', - flexDirection: 'column', - flex: 1, - marginLeft: '12mm', }, name: { - fontSize: '28pt', + fontSize: '15pt', fontWeight: 800, - letterSpacing: '-0.02em', color: PRINT_COLORS.black, - marginBottom: '1mm', + textAlign: 'center', + marginBottom: '1.5mm', + letterSpacing: '-0.02em', + lineHeight: 1.2, }, title: { - fontSize: '12pt', - fontWeight: 700, - color: PRINT_COLORS.muted, - letterSpacing: '0.08em', - textTransform: 'uppercase', - }, - contactInfo: { - textAlign: 'right', - fontSize: '9pt', + fontSize: '7pt', fontWeight: 600, - color: PRINT_COLORS.black, - lineHeight: '1.8', - }, - headerLine: { - width: '100%', - height: '1.5px', - backgroundColor: PRINT_COLORS.black, - margin: '8mm 0 4mm 0', - position: 'relative', - }, - headerLineAccent: { - position: 'absolute', - right: '0', - top: '0', - width: '30%', - height: '1.5px', - backgroundColor: PRINT_COLORS.accent, - }, - summaryHeadline: { - fontSize: '22pt', - fontWeight: 800, - lineHeight: '1.2', - marginBottom: '8mm', - color: PRINT_COLORS.black, - maxWidth: '90%', - }, - summaryParagraph: { - fontSize: '11pt', color: PRINT_COLORS.grey, - marginBottom: '12mm', - textAlign: 'justify', - lineHeight: '1.7', - whiteSpace: 'pre-wrap', - }, - skillsGrid: { - display: 'grid', - gridTemplateColumns: 'repeat(3, 1fr)', - gap: '3mm 10mm', - }, - skillItem: { - fontSize: '10pt', - display: 'flex', - alignItems: 'center', - gap: '4mm', - fontWeight: 600, - }, - skillIcon: { - width: '6mm', - height: '1.5px', - backgroundColor: PRINT_COLORS.accent, - }, - mainBody: { - padding: '0 18mm', - flex: 1, - }, - sectionHeading: { - fontSize: '11pt', - fontWeight: 900, + textAlign: 'center', textTransform: 'uppercase', - letterSpacing: '0.2em', - marginBottom: '10mm', + letterSpacing: '0.1em', + lineHeight: 1.4, + }, + contactSection: { + borderTop: `1px solid ${PRINT_COLORS.border}`, + paddingTop: '5mm', + }, + sectionLabel: { + fontSize: '6.5pt', + fontWeight: 700, + textTransform: 'uppercase', + letterSpacing: '0.12em', color: PRINT_COLORS.black, - borderLeft: `5mm solid ${PRINT_COLORS.accent}`, - paddingLeft: '5mm', - pageBreakAfter: 'avoid', - breakAfter: 'avoid' + marginBottom: '3.5mm', + borderBottom: `1px solid ${PRINT_COLORS.black}`, + paddingBottom: '1.5mm', }, - experienceRow: { - display: 'grid', - gridTemplateColumns: '1.3fr 2fr', - gap: '12mm', - marginBottom: '12mm', + contactItem: { + fontSize: '6.5pt', + color: PRINT_COLORS.grey, + marginBottom: '1.8mm', + wordBreak: 'break-word', + lineHeight: 1.4, }, - experienceMeta: { + skillTag: { + display: 'inline-block', + background: PRINT_COLORS.lightBg, + border: `1px solid ${PRINT_COLORS.border}`, + color: PRINT_COLORS.secondary, + padding: '1.5mm 2.5mm', + fontSize: '5.5pt', + fontWeight: 500, + marginBottom: '1.8mm', + marginRight: '1.8mm', + borderRadius: '0.5mm', + fontFamily: "'JetBrains Mono', monospace", + }, + profileInitial: { + fontSize: '18pt', + fontWeight: 700, + color: PRINT_COLORS.primary, + }, + sidebarAccent: { + position: 'absolute', + top: 0, + left: 0, + width: '3mm', + height: '100%', + background: PRINT_COLORS.black, + }, + mainContent: { + flex: 1, + padding: '10mm 10mm', display: 'flex', flexDirection: 'column', + gap: '5mm', + }, + mainTitle: { + fontSize: '16pt', + fontWeight: 800, + color: PRINT_COLORS.primary, + marginBottom: '2mm', + letterSpacing: '-0.02em', + lineHeight: 1.2, + }, + subtitle: { + fontSize: '9pt', + fontWeight: 600, + color: PRINT_COLORS.grey, + textTransform: 'uppercase', + letterSpacing: '0.1em', + marginBottom: '5mm', + }, + summaryParagraph: { + fontSize: '9pt', + lineHeight: 1.6, + textAlign: 'justify', + color: PRINT_COLORS.grey, + marginBottom: '4mm', + }, + sectionHeading: { + fontSize: '9pt', + fontWeight: 800, + textTransform: 'uppercase', + letterSpacing: '0.15em', + color: PRINT_COLORS.primary, + paddingBottom: '2mm', + borderBottom: `1.5px solid ${PRINT_COLORS.black}`, + marginBottom: '5mm', + marginTop: '1mm', + }, + experienceItem: { + marginBottom: '5mm', + }, + expHeader: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'flex-start', + marginBottom: '0.5mm', }, expRole: { - fontSize: '13pt', - fontWeight: 800, - marginBottom: '2mm', - }, - expCompany: { - fontSize: '10.5pt', - fontWeight: 600, - color: PRINT_COLORS.muted, + fontSize: '9pt', + fontWeight: 700, + color: PRINT_COLORS.primary, + lineHeight: 1.3, }, expPeriod: { - fontSize: '9.5pt', + fontSize: '6.5pt', + fontWeight: 600, color: PRINT_COLORS.muted, - marginTop: '3mm', - fontFamily: 'monospace', - fontWeight: 'bold', + fontFamily: "'JetBrains Mono', monospace", + whiteSpace: 'nowrap', }, - expHighlights: { + expCompany: { + fontSize: '7.5pt', + fontWeight: 600, + color: PRINT_COLORS.muted, + marginBottom: '1.5mm', + }, + highlightList: { margin: 0, - paddingLeft: '6mm', - listStyleType: 'none', + paddingLeft: '4mm', }, highlightItem: { - marginBottom: '4mm', - textAlign: 'justify', - fontSize: '10.5pt', + fontSize: '7.5pt', + lineHeight: 1.45, + color: PRINT_COLORS.grey, + marginBottom: '0.8mm', position: 'relative', - lineHeight: '1.6', - pageBreakInside: 'avoid', - breakInside: 'avoid' }, highlightBullet: { position: 'absolute', - left: '-6mm', - top: '2.5mm', - width: '3.5mm', - height: '1px', - backgroundColor: PRINT_COLORS.accent, + left: '-2.5mm', + top: '2mm', + width: '1.5mm', + height: '1.5mm', + borderRadius: '50%', + background: PRINT_COLORS.black, }, - techMeta: { - fontFamily: 'monospace', - fontSize: '10pt', - color: PRINT_COLORS.black, - backgroundColor: PRINT_COLORS.white, - padding: '1.5mm 3mm', - borderLeft: `2px solid ${PRINT_COLORS.accent}`, - border: `1px solid ${PRINT_COLORS.border}`, + techBadge: { + fontFamily: "'JetBrains Mono', monospace", + fontSize: '6.5pt', + color: PRINT_COLORS.primary, + background: PRINT_COLORS.lightBg, + padding: '1mm 2mm', + borderLeft: `2px solid ${PRINT_COLORS.black}`, + marginTop: '2mm', + display: 'inline-block', + }, + projectCard: { + padding: '2.5mm', + marginBottom: '2.5mm', + borderLeft: `1.5px solid ${PRINT_COLORS.black}`, + }, + projectTitle: { + fontSize: '7.5pt', + fontWeight: 700, + color: PRINT_COLORS.primary, + marginBottom: '0.5mm', + }, + projectDesc: { + fontSize: '6pt', + color: PRINT_COLORS.grey, + lineHeight: 1.45, + marginBottom: '1mm', + }, + projectTech: { + fontFamily: "'JetBrains Mono', monospace", + fontSize: '5pt', + color: PRINT_COLORS.secondary, + padding: '0.3mm 1mm', + display: 'inline-block', + fontWeight: 600, + }, + educationSection: { + display: 'flex', + gap: '4mm', + alignItems: 'flex-start', + }, + educationSchool: { + fontSize: '8pt', + fontWeight: 700, + color: PRINT_COLORS.primary, + }, + educationDegree: { + fontSize: '7pt', + color: PRINT_COLORS.grey, + }, + educationPeriod: { + fontSize: '6pt', + color: PRINT_COLORS.muted, + fontFamily: "'JetBrains Mono', monospace", + }, + awardItem: { + display: 'flex', + alignItems: 'center', + gap: '3mm', + fontSize: '7pt', marginTop: '3mm', }, - footer: { - marginTop: '10mm', - padding: '10mm 18mm', - textAlign: 'center', - fontSize: '9pt', - color: PRINT_COLORS.muted, - borderTop: `1px solid ${PRINT_COLORS.border}`, - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - pageBreakInside: 'avoid', - breakInside: 'avoid' - } + awardDot: { + width: '2mm', + height: '2mm', + background: PRINT_COLORS.black, + borderRadius: '50%', + }, }; export default function PrintPortfolio() { @@ -309,55 +404,73 @@ export default function PrintPortfolio() { return (
-
- {/* --- HEADER --- */} -
-
-
- -
-
-

{PRINT_PERSONAL_INFO.name}

-
{PRINT_PERSONAL_INFO.title}
-
-
-
{PRINT_PERSONAL_INFO.email}
-
{PRINT_PERSONAL_INFO.phone}
-
{PRINT_PERSONAL_INFO.portfolio}
-
+ {/* LEFT SIDEBAR */} + + + {/* MAIN CONTENT */} +
+
+

{PRINT_PERSONAL_INFO.summaryHeadline}

+

{PRINT_PERSONAL_INFO.summaryBody}

+
+ +
+

Experience Journey

{PRINT_EXPERIENCES.map((exp, i) => ( -
-
-
{exp.role}
-
{exp.company}
+
+
+
+
{exp.role}
+
{exp.company}
+
{exp.period}
-
    +
      {exp.highlights && exp.highlights.map((h, j) => (
    • @@ -367,52 +480,52 @@ export default function PrintPortfolio() {
))} +
-
Branded Systems & Tech
+
+

Strategic Projects

{PRINT_STRATEGIC_TECH.map((proj, i) => ( -
-
-
{proj.name}
-
{proj.tech}
-
-
- {proj.desc} -
+
+
{proj.name}
+
{proj.desc}
+ {proj.tech}
))} +
-
Foundation & Recognition
-
-
-
{PRINT_EDUCATION[0].school}
-
{PRINT_EDUCATION[0].period}
-
-
- {PRINT_EDUCATION[0].degree} ({PRINT_EDUCATION[0].details})
-
-
-
-
- P&G SEA Digital Awards (Best Digital Campaign, 2024) -
-
+
+
+
+
Recognition & Awards
+
+
+ P&G SEA Digital Awards - Best Digital Campaign, 2024 +
+
+
+ RMIT Student Showcase - Best Artistic Work: Hyper-realistic Hand-drawing
- {/* --- FOOTER --- */} -
-
- {PRINT_PERSONAL_INFO.linkedin} - {PRINT_PERSONAL_INFO.portfolio} +
+
+ {PRINT_PERSONAL_INFO.email} • {PRINT_PERSONAL_INFO.phone}
-
- - VNDK // CV +
+ + KHOA.VO
-
+
); -} +} \ No newline at end of file diff --git a/src/print.css b/src/print.css index 33c2c41..d638a59 100644 --- a/src/print.css +++ b/src/print.css @@ -1,30 +1,18 @@ -/* ============================================ - PRINT STYLES - A4 Portfolio PDF - ============================================ */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap'); -/* Hide by default on screen so it doesn't break web app layout */ .print-portfolio { - display: none; -} - -.print-portfolio.show-preview { - display: flex !important; - flex-direction: column; - background: #0f172a; - padding: 40px 0; - min-height: 100vh; - align-items: center; -} - -.print-portfolio.show-preview .cv-page { - box-shadow: 0 30px 60px rgba(0, 0, 0, 0.5); - margin-bottom: 40px; + position: absolute; + left: -9999px; + top: 0; + width: 0; + height: 0; + overflow: hidden; } @media print { @page { size: A4; - margin: 15mm; + margin: 0; } html, body { @@ -36,7 +24,6 @@ print-color-adjust: exact !important; } - /* Hide everything except the print-portfolio container */ body > *:not(#root) { display: none !important; } @@ -53,11 +40,18 @@ } .print-portfolio { - display: block !important; - width: 100% !important; - margin: 0 auto !important; - padding: 0 !important; - background: #ffffff; position: static !important; + left: auto !important; + width: 100% !important; + height: auto !important; + overflow: visible !important; + background: #ffffff; } -} + + .print-portfolio-content { + display: flex !important; + flex-direction: row !important; + width: 100% !important; + min-height: 100vh !important; + } +} \ No newline at end of file