feat(resume): enhance spacing, styling, and add markdown text parsing

- Update section spacing from mb-8 to mb-12 for improved visual hierarchy
- Refine heading padding and margins for consistent section styling
- Adjust work experience highlights spacing and add leading-relaxed for readability
- Implement markdown bold text parsing in job highlights using new composable
- Add color-coded icons in resume header (red for location, green for phone, amber for email, blue for website)
- Reorganize contact information into two lines for better layout organization
- Standardize icon sizing with explicit w-4 h-4 dimensions across header
- Create useMarkdownText composable for markdown formatting utilities
- Update resume data and PDF export endpoint to support enhanced styling
- Improve print styling consistency across all resume components
This commit is contained in:
mahdiarghyani
2025-12-06 14:04:45 +03:30
parent 73afa9f5a6
commit 12c8847447
9 changed files with 130 additions and 51 deletions
@@ -9,15 +9,15 @@ const props = defineProps<Props>()
</script>
<template>
<section class="mb-8 print:mb-5">
<section class="mb-12 print:mb-12">
<h2
class="text-base font-bold text-blue-700 uppercase border-b-2 border-blue-600 pb-1.5 mb-4 print:mb-2.5 tracking-wide">
class="text-base font-bold text-blue-700 uppercase border-b-2 border-blue-600 pb-2 mb-6 print:mb-6 tracking-wide">
Skills & Qualifications
</h2>
<!-- Technical Skills (categorized) -->
<div v-if="skills?.length">
<div class="space-y-2">
<div class="space-y-3 print:space-y-3">
<div v-for="skill in skills" :key="skill.name" class="text-sm">
<span class="font-bold text-gray-900">{{ skill.name }}:&nbsp;</span><span class="text-gray-700">{{
skill.keywords.join(', ') }}</span>
+3 -3
View File
@@ -16,13 +16,13 @@ function formatDateRange(start: string, end?: string): string {
</script>
<template>
<section class="mb-8 print:mb-5">
<section class="mb-12 print:mb-12">
<h2
class="text-base font-bold text-blue-700 uppercase border-b-2 border-blue-600 pb-1.5 mb-4 print:mb-2.5 tracking-wide">
class="text-base font-bold text-blue-700 uppercase border-b-2 border-blue-600 pb-2 mb-6 print:mb-6 tracking-wide">
Education
</h2>
<div v-for="edu in education" :key="edu.institution + edu.startDate" class="mb-4 last:mb-0">
<div v-for="edu in education" :key="edu.institution + edu.startDate" class="mb-4 last:mb-0 print:mb-4">
<div class="flex justify-between items-start">
<div>
<h3 class="text-sm font-bold text-gray-900">{{ edu.studyType }} in {{ edu.area }}</h3>
+7 -7
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import type { WorkExperience } from '~/types/resume'
import { parseMarkdownBold } from '~/composables/useMarkdownText'
interface Props {
work: WorkExperience[]
@@ -24,13 +25,13 @@ function formatDateRange(start: string, end?: string): string {
</script>
<template>
<section class="mb-8 print:mb-5">
<section class="mb-12 print:mb-12">
<h2
class="text-base font-bold text-blue-700 uppercase border-b-2 border-blue-600 pb-1.5 mb-4 print:mb-2.5 tracking-wide">
class="text-base font-bold text-blue-700 uppercase border-b-2 border-blue-600 pb-2 mb-6 print:mb-6 tracking-wide">
Work Experience
</h2>
<div v-for="job in sortedWork" :key="job.company + job.startDate" class="mb-5 last:mb-0 print:mb-4">
<div v-for="job in sortedWork" :key="job.company + job.startDate" class="mb-8 last:mb-0 print:mb-8">
<div class="flex justify-between items-start gap-4">
<div class="flex-1 min-w-0">
<h3 class="text-sm font-bold text-gray-900 break-words hyphens-none">{{ job.position }}</h3>
@@ -40,11 +41,10 @@ function formatDateRange(start: string, end?: string): string {
{{ formatDateRange(job.startDate, job.endDate) }}
</span>
</div>
<ul class="mt-2.5 space-y-1.5 print:mt-1.5 print:space-y-1">
<ul class="mt-4 space-y-2.5 print:mt-4 print:space-y-2.5">
<li v-for="(highlight, idx) in job.highlights" :key="idx"
class="text-sm text-gray-700 pl-4 relative before:content-['•'] before:absolute before:left-0 before:text-gray-500 before:font-bold print:before:text-gray-500 break-words hyphens-none">
{{ highlight }}
</li>
class="text-sm text-gray-700 pl-4 relative before:content-['•'] before:absolute before:left-0 before:text-gray-500 before:font-bold print:before:text-gray-500 break-words hyphens-none leading-relaxed"
v-html="parseMarkdownBold(highlight)" />
</ul>
</div>
</section>
+11 -7
View File
@@ -9,7 +9,7 @@ defineProps<Props>()
</script>
<template>
<div class="flex gap-6 mb-8 print:mb-4 print:pb-1">
<div class="flex gap-6 mb-10 print:mb-8 print:pb-2">
<!-- Profile Photo (if provided) -->
<div v-if="basics.image" class="flex-shrink-0">
<img :src="basics.image" :alt="basics.name" class="w-32 h-32 object-cover rounded-full print:w-24 print:h-24" />
@@ -20,31 +20,35 @@ defineProps<Props>()
<h1 class="text-3xl font-bold text-gray-900 print:text-xl print:mb-0.5">{{ basics.name }}</h1>
<p class="text-xl text-gray-600 mt-1 print:text-base print:mt-0">{{ basics.label }}</p>
<!-- Line 1: Location, Phone, Email -->
<div class="mt-3 flex flex-wrap gap-x-5 gap-y-2 text-sm text-gray-700 print:mt-2 print:text-xs print:gap-x-4">
<!-- Location -->
<span class="inline-flex items-center gap-1.5">
<UIcon name="i-heroicons-map-pin" class="text-gray-500 flex-shrink-0" />
<UIcon name="i-heroicons-map-pin" class="text-red-500 flex-shrink-0 w-4 h-4" />
{{ basics.location.city }}, {{ basics.location.country }}
</span>
<!-- Phone -->
<a :href="`tel:${basics.phone}`" class="inline-flex items-center gap-1.5 hover:text-blue-600 transition-colors">
<UIcon name="i-heroicons-phone" class="text-gray-500 flex-shrink-0" />
<UIcon name="i-heroicons-phone" class="text-green-600 flex-shrink-0 w-4 h-4" />
{{ basics.phone }}
</a>
<!-- Email -->
<a :href="`mailto:${basics.email}`"
class="inline-flex items-center gap-1.5 hover:text-blue-600 transition-colors">
<UIcon name="i-heroicons-envelope" class="text-gray-500 flex-shrink-0" />
<UIcon name="i-heroicons-envelope" class="text-amber-500 flex-shrink-0 w-4 h-4" />
{{ basics.email }}
</a>
</div>
<!-- Line 2: Website, LinkedIn, GitHub -->
<div class="mt-2 flex flex-wrap gap-x-5 gap-y-2 text-sm text-gray-700 print:mt-1 print:text-xs print:gap-x-4">
<!-- Website (Portfolio) - Bold & Colored -->
<a v-if="basics.profiles?.find(p => p.network === 'Portfolio')"
:href="basics.profiles.find(p => p.network === 'Portfolio')!.url" target="_blank"
class="inline-flex items-center gap-1.5 font-semibold text-blue-600 hover:text-blue-700 transition-colors print:text-blue-600">
<UIcon name="i-heroicons-globe-alt" class="text-blue-600 flex-shrink-0" />
<UIcon name="i-heroicons-globe-alt" class="text-blue-600 flex-shrink-0 w-4 h-4" />
Website
</a>
@@ -52,7 +56,7 @@ defineProps<Props>()
<a v-if="basics.profiles?.find(p => p.network === 'LinkedIn')"
:href="basics.profiles.find(p => p.network === 'LinkedIn')!.url" target="_blank"
class="inline-flex items-center gap-1.5 hover:text-blue-600 transition-colors">
<UIcon name="i-mdi-linkedin" class="text-gray-500 flex-shrink-0" />
<UIcon name="i-mdi-linkedin" class="text-[#0A66C2] flex-shrink-0 w-4 h-4" />
LinkedIn
</a>
@@ -60,7 +64,7 @@ defineProps<Props>()
<a v-if="basics.profiles?.find(p => p.network === 'GitHub')"
:href="basics.profiles.find(p => p.network === 'GitHub')!.url" target="_blank"
class="inline-flex items-center gap-1.5 hover:text-blue-600 transition-colors">
<UIcon name="i-mdi-github" class="text-gray-500 flex-shrink-0" />
<UIcon name="i-mdi-github" class="text-gray-800 flex-shrink-0 w-4 h-4" />
GitHub
</a>
</div>
+3 -3
View File
@@ -10,14 +10,14 @@ const props = defineProps<Props>()
</script>
<template>
<section class="mb-8 print:mb-5">
<section class="mb-12 print:mb-12">
<h2
class="text-base font-bold text-blue-700 uppercase border-b-2 border-blue-600 pb-1.5 mb-4 print:mb-2.5 tracking-wide">
class="text-base font-bold text-blue-700 uppercase border-b-2 border-blue-600 pb-2 mb-6 print:mb-6 tracking-wide">
Languages & Certifications
</h2>
<!-- Languages -->
<div v-if="languages?.length" class="mb-3">
<div v-if="languages?.length" class="mb-2 print:mb-2">
<span class="text-sm font-bold text-gray-900">Languages: </span>
<span class="text-sm text-gray-700">
<template v-for="(lang, idx) in languages" :key="lang.language">
+8 -4
View File
@@ -1,17 +1,21 @@
<script setup lang="ts">
import { parseMarkdownBold } from '~/composables/useMarkdownText'
interface Props {
summary: string
}
defineProps<Props>()
const props = defineProps<Props>()
const parsedSummary = computed(() => parseMarkdownBold(props.summary))
</script>
<template>
<section class="mb-8 print:mb-5">
<section class="mb-12 print:mb-12">
<h2
class="text-base font-bold text-blue-700 uppercase border-b-2 border-blue-600 pb-1.5 mb-3 print:mb-2 tracking-wide">
class="text-base font-bold text-blue-700 uppercase border-b-2 border-blue-600 pb-2 mb-5 print:mb-5 tracking-wide">
Summary
</h2>
<p class="text-sm text-gray-800 leading-relaxed print:leading-normal">{{ summary }}</p>
<p class="text-sm text-gray-800 leading-loose print:leading-loose" v-html="parsedSummary" />
</section>
</template>
+22
View File
@@ -0,0 +1,22 @@
/**
* Utility function for parsing simple markdown bold syntax (**text**) to HTML
* Used in resume components to highlight important keywords
* SSR-compatible - works on both server and client
*/
/**
* Converts **text** to <strong>text</strong>
*/
export function parseMarkdownBold(text: string): string {
if (!text) return ''
return text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
}
/**
* Composable wrapper for Vue components
*/
export function useMarkdownText() {
return {
parseMarkdownBold,
}
}
+22 -20
View File
@@ -42,7 +42,7 @@ export const resumeData: Resume = {
},
],
summary:
'Frontend Developer with 3+ years building high-performance Vue.js/Nuxt applications for international clients. AI-first engineer leveraging Cursor, GitHub Copilot, and AI-powered tools & methodologies to accelerate development by up to 50% while maintaining code quality. Specialized in performance optimization, scalable architecture (SSR, PWA, RBAC), and accessibility. Delivered 4+ production applications with focus on maintainability and distributed team collaboration. Fluent in English (6+ years in fully English-speaking environment at Huawei).',
'Frontend Developer with **3+ years** building high-performance Vue.js/Nuxt applications for international clients. **AI-first engineer** leveraging **Cursor**, **GitHub Copilot**, and AI-powered tools & methodologies to accelerate development by up to **50%** while maintaining code quality. Specialized in performance optimization, scalable architecture (SSR, PWA, RBAC), and accessibility. Delivered **4+ production applications** with focus on maintainability and distributed team collaboration. Fluent in English (**6+ years** in fully English-speaking environment at Huawei).',
},
work: [
@@ -52,12 +52,12 @@ export const resumeData: Resume = {
location: 'Izmir, Turkey',
startDate: '2024-12',
highlights: [
'Architected two medical tourism platforms (Elara Medical & Artemis Clinics) serving international patients across 10+ languages with real-time scheduling via WebSocket, multi-language support (3 languages), and PWA capabilities for offline access',
'Accelerated feature development at least by 50% using Cursor and other tools for component generation and real-time debugging, while maintaining zero critical bugs through AI-assisted code review and automated testing',
'Architected **two medical tourism platforms** (**Elara Medical** & **Artemis Clinics**) serving international patients across **10+ languages** with real-time scheduling via WebSocket, multi-language support (3 languages), and PWA capabilities for offline access',
'Accelerated feature development by **50%** using **Cursor** and other tools for component generation and real-time debugging, while maintaining **zero critical bugs** through AI-assisted code review and automated testing',
'Optimized application performance through code splitting, lazy loading, and Pinia state management restructuring, significantly reducing bundle size and improving initial load time',
'Engineered RBAC (Role-Based Access Control) system with role-based permissions, multi-tenant architecture supporting 3 languages (English, Persian, Turkish), and PWA features enabling offline functionality across mobile and desktop devices',
'Established code quality standards with ESLint and implementing CI/CD automation with GitHub Actions to ensure stable releases',
'Collaborated with CEO on product strategy and frontend architecture, maintaining full visibility into product roadmap',
'Engineered **RBAC** (Role-Based Access Control) system with role-based permissions, multi-tenant architecture supporting **3 languages** (English, Persian, Turkish), and PWA features enabling offline functionality across mobile and desktop devices',
'Established code quality standards with ESLint and implementing CI/CD automation with **GitHub Actions** to ensure stable releases',
'Demonstrated **product-minded engineering** by collaborating directly with CEO on product strategy and frontend architecture, ensuring technical decisions aligned with business goals',
],
},
{
@@ -67,13 +67,12 @@ export const resumeData: Resume = {
startDate: '2023-09',
endDate: '2024-12',
highlights: [
'Delivered 4+ production-grade web applications for international clients using Vue.js/Nuxt.js, consistently achieving strong performance metrics and high client satisfaction',
'Leveraged AI development tools (Cursor and Github Copilot) to reduce development time by at least 40% across 6 projects, enabling faster client delivery and 100% on-time project completion rate',
'Elara Medical - Medical tourism platform with RBAC system, i18n for 3 languages, PWA features, and optimized performance through code splitting and lazy loading',
'Ideh - Idea evaluation platform with reusable component library reducing development time by 30% and scalable Vue.js architecture',
'Insho - Advertising marketplace with responsive UI, matching engine for campaign collaboration, and comprehensive listing/proposal flows',
'BaMashin - Mobility rental platform with payment integration and responsive UI optimized for mobile/desktop',
'Additional Projects: 2 custom B2B SaaS applications with real-time collaboration, analytics dashboards, and third-party API integrations',
'Delivered **4+ production-grade** web applications for international clients using Vue.js/Nuxt.js, consistently achieving strong performance metrics and high client satisfaction',
'Leveraged AI development tools (**Cursor** and **GitHub Copilot**) to reduce development time by **40%** across **6 projects**, enabling faster client delivery and **100% on-time** project completion rate',
'**Ideh** - Idea evaluation platform with reusable component library reducing development time by **30%** and scalable Vue.js architecture',
'**Insho** - Advertising marketplace with responsive UI, matching engine for campaign collaboration, and comprehensive listing/proposal flows',
'**BaMashin** - Mobility rental platform with payment integration and responsive UI optimized for mobile/desktop',
'Additional Projects: **2 custom B2B SaaS** applications with real-time collaboration, analytics dashboards, and third-party API integrations',
'Led client communications, translated business requirements into technical specifications, delivered iteratively with clear documentation and transparent progress updates',
],
},
@@ -84,22 +83,25 @@ export const resumeData: Resume = {
startDate: '2022-04',
endDate: '2023-08',
highlights: [
'Led team managing performance and availability for 14,500+ network sites nationwide, ensuring 99.5%+ uptime and rapid incident response across distributed infrastructure',
'Leveraged AI-powered analytics tools for pattern recognition in network performance data, identifying optimization opportunities 50% faster than manual analysis methods',
'Established operational standards and best practices; mentored 5 team members improving team efficiency by 30% through process optimization and knowledge sharing',
'Conducted comprehensive data analysis and KPI monitoring using advanced analytics; identified performance trends and improvement opportunities, reducing network downtime by 22% year-over-year',
'Owned stakeholder communication interface; delivered weekly performance reports and monthly strategic updates to C-level executives, translating technical metrics into business impact',
'Led team managing performance and availability for **14,500+ network sites** nationwide, ensuring **99.5%+ uptime** and rapid incident response across distributed infrastructure',
'Leveraged **AI-powered analytics** tools for pattern recognition in network performance data, identifying optimization opportunities **50% faster** than manual analysis methods',
'Established operational standards and best practices; mentored **5 team members** improving team efficiency by **30%** through process optimization and knowledge sharing',
'Conducted comprehensive data analysis and KPI monitoring using advanced analytics; identified performance trends and improvement opportunities, reducing network downtime by **22%** year-over-year',
'Owned stakeholder communication interface; delivered weekly performance reports and monthly strategic updates to **C-level executives**, translating technical metrics into business impact',
'Built strong operational discipline (documentation, monitoring, incident management, quality gates) that directly enhances frontend engineering quality and architectural decision-making',
],
},
{
company: 'Huawei Technologies',
position: 'Various Technical & Leadership Roles',
position: 'Technical & Leadership Roles',
location: 'Tehran, Iran',
startDate: '2016-06',
endDate: '2022-04',
highlights: [
'Progressed through multiple technical and leadership positions in telecom operations (details available upon request)',
'**Senior Performance Analyst** (2018-2022): Drove network KPI analysis across 2G/3G/LTE; contributed to audits, process improvements and performance dashboards',
'**Assistant Regional Manager** (2018): Managed ~3,000 BTS sites across Tehran Province; coordinated subcontractors and translated technical specs into implementation plans',
'**TCHA Team Lead** (2017-2018): Built availability dashboards and drove stakeholder alignment; recognized as outstanding fresh graduate at Huawei annual meeting',
'**Back Office Operations** (2016-2017): Supported OSS operations, performance checks and reporting; contributed to team efficiency and customer satisfaction',
],
},
],
+51 -4
View File
@@ -51,12 +51,20 @@ export default defineEventHandler(async (event) => {
throw new Error(`Failed to load: ${response?.status()}`)
}
// Inject critical CSS fixes
// Wait for Vue hydration and v-html rendering to complete
await page.waitForSelector('strong', { timeout: 5000 }).catch(() => {
console.log('[PDF API] No strong tags found, continuing anyway')
})
// Additional wait to ensure all dynamic content is rendered
await new Promise(resolve => setTimeout(resolve, 1000))
// Inject critical CSS fixes for PDF generation - optimized for exactly 2 pages
await page.addStyleTag({
content: `
@page {
size: A4;
margin: 1.2cm;
margin: 1cm;
}
* {
@@ -65,6 +73,8 @@ export default defineEventHandler(async (event) => {
hyphens: none !important;
-webkit-hyphens: none !important;
-ms-hyphens: none !important;
print-color-adjust: exact !important;
-webkit-print-color-adjust: exact !important;
}
html, body {
@@ -90,18 +100,55 @@ export default defineEventHandler(async (event) => {
}
.resume-content {
padding: 0 !important;
padding: 1rem !important;
}
/* Balanced spacing for sections - fits exactly 2 pages */
section {
margin-bottom: 1.5rem !important;
page-break-inside: auto !important;
break-inside: auto !important;
}
h2 {
section:last-child {
margin-bottom: 0 !important;
}
/* Section titles */
section h2 {
margin-bottom: 0.7rem !important;
padding-bottom: 0.3rem !important;
page-break-after: avoid !important;
break-after: avoid !important;
}
/* Work experience job blocks */
section > div {
margin-bottom: 1rem !important;
}
section > div:last-child {
margin-bottom: 0 !important;
}
/* Bullet lists */
ul {
margin-top: 0.5rem !important;
}
ul li {
margin-bottom: 0.25rem !important;
line-height: 1.45 !important;
}
strong {
font-weight: 700 !important;
}
/* Summary paragraph - more line height */
section > p {
line-height: 1.75 !important;
}
`,
})