# Epic Technical Specification: Resume Preview Page Date: 2025-11-30 Author: mahdi Epic ID: 2 Status: **REVISED** (Post UX Validation) Last Update: 2025-11-30 (Winston - Architect) --- ## Overview Epic 2 delivers the Resume Preview Page - a pixel-perfect, standalone web page at `/resume` that renders the resume exactly as it will appear in the PDF export. This epic creates the visual foundation of the Resume Export feature, implementing all UI components with a **single-column layout** using the Blue & White Clean Professional design template. The preview page serves dual purposes: (1) user-facing preview for verification before download, and (2) source page for server-side PDF generation via Puppeteer. The WYSIWYG (What You See Is What You Get) approach ensures perfect consistency between web and PDF output. **Design Template Reference:** `design templates/Blue and White Clean and Professional Resume.png` ## Objectives and Scope **In Scope:** - Standalone `/resume` route with no site navigation - **Single-column vertical layout** with A4-proportioned container - Five modular Vue components for resume sections - Blue (#2563eb) and white color scheme with Inter typography - Blue uppercase section headers with bottom border - Profile photo display in header - Print-optimized CSS for PDF generation compatibility - A4 aspect ratio container (210mm × 297mm) - Floating download button (hidden in print mode via `?print=true`) - ATS-compatible HTML structure (semantic headings, no layout tables) **Out of Scope:** - PDF generation logic (Epic 3) - Resume data creation (Epic 1 - already completed) - Persian language support (future) - Multiple template designs (future) - Customization UI (future) ## System Architecture Alignment **Framework:** Nuxt 4.1.3 with Vue 3 Composition API **Styling:** Tailwind CSS 4.1.x with Nuxt UI 4.0.x components **Fonts:** Inter (via @nuxt/fonts) for English text **Icons:** Nuxt UI Icon component with Iconify icons **Architecture Decisions Referenced:** - ADR-002: Vue Components as Templates - Each resume section is a standalone component - Novel Pattern: WYSIWYG PDF Export - Single component source for web and PDF - Naming Conventions: PascalCase for components, Tailwind utilities for styling **Data Source:** `useResumeData()` composable (from Epic 1) provides reactive access to `app/data/resume.en.ts` ## Detailed Design ### Services and Modules | Component | Responsibility | Inputs | Outputs | Owner | |-----------|---------------|--------|---------|-------| | `pages/resume.vue` | Route handler, layout wrapper | Query param `?print` | Renders ResumePreview | Story 2.1 | | `ResumePreview.vue` | Container, single-column layout | Resume data | Full resume layout | Story 2.2 | | `ResumeHeader.vue` | Photo + Name + Contact Info | `basics` (name, label, email, phone, location, url, image) | Header section | Story 2.3 | | `ResumeSummary.vue` | Professional summary | `basics.summary` | Summary section | Story 2.3 | | `ResumeExperience.vue` | Work history with highlights | `work[]` array | Experience section | Story 2.3 | | `ResumeEducation.vue` | Education history | `education[]` array | Education section | Story 2.4 | | `ResumeAdditionalInfo.vue` | Skills + Languages + Certs | `skills[]`, `languages[]`, `certificates[]` | Additional Info section | Story 2.4 | | `ResumeDownloadButton.vue` | Floating action button | `?print` query param | Download trigger | Story 2.5 | **Removed Components (vs. Previous Draft):** - `ResumeContact.vue` - Merged into `ResumeHeader.vue` - `ResumeSkills.vue` - Merged into `ResumeAdditionalInfo.vue` - `ResumeLanguages.vue` - Merged into `ResumeAdditionalInfo.vue` ### Data Models and Contracts **Input:** All components consume data from `useResumeData()` composable: ```typescript const { resume, formatDate, getFullName, getPdfFilename } = useResumeData() ``` **Resume Data Structure (from Epic 1):** - `resume.basics`: Name, label, email, phone, location, url, image, summary - `resume.work[]`: Company, position, startDate, endDate, highlights[] - `resume.education[]`: Institution, area, studyType, startDate, endDate - `resume.skills[]`: Name (category), keywords[] - `resume.languages[]`: Language, fluency - `resume.certificates[]`: Name, date, issuer (optional) - `resume.awards[]`: Title, date, awarder, summary (optional) **Date Format:** YYYY-MM strings formatted to "Jan 2023" via `formatDate()` helper ### APIs and Interfaces **Component Props:** ```typescript // ResumePreview.vue - No props (uses composable) // All child components receive data via props from parent // ResumeHeader.vue interface Props { name: string label: string // Job title email: string phone: string location: string url: string image?: string // Profile photo URL } // ResumeSummary.vue interface Props { summary: string } // ResumeExperience.vue interface Props { work: WorkExperience[] } // ResumeEducation.vue interface Props { education: Education[] } // ResumeAdditionalInfo.vue interface Props { skills: Skill[] languages: Language[] certificates?: Certificate[] awards?: Award[] } // ResumeDownloadButton.vue interface Props { isPrintMode: boolean } ``` **Query Parameters:** - `?print=true`: Hides download button, optimizes for PDF generation ### Workflows and Sequencing **User Flow:** 1. User navigates to `/resume` 2. Page loads with `layout: false` (standalone) 3. `ResumePreview.vue` fetches data via `useResumeData()` 4. Single-column layout renders in order: - Header (Photo + Name + Contact Info) - Summary - Experience - Education - Additional Information (Skills, Languages, Certs) 5. Floating download button appears (bottom-right) 6. User clicks download → triggers Epic 3 PDF generation **PDF Generation Flow (Epic 3 integration):** 1. Puppeteer navigates to `/resume?print=true` 2. Same components render without download button 3. Puppeteer captures page as PDF 4. Result: Pixel-perfect match to web preview ## Non-Functional Requirements ### Performance - **Page Load:** < 1 second (static data, no API calls) - **LCP (Largest Contentful Paint):** < 1.5s - **CLS (Cumulative Layout Shift):** 0 (fixed dimensions) - **Font Loading:** Inter font preloaded via @nuxt/fonts **Strategy:** Inline critical CSS, use Tailwind JIT, no external API dependencies ### Security - **No PII Exposure:** Resume data is static, no user input - **SEO:** `noindex` meta tag for privacy (resume not indexed by search engines) - **XSS Protection:** Vue's automatic escaping for all text content ### Reliability/Availability - **Static Rendering:** Page works without JavaScript (SSR) - **Graceful Degradation:** Print styles work even if JS fails - **Error Handling:** Composable returns empty data if file missing (prevents crashes) ### Observability - **Console Logging:** Development mode logs component mount/unmount - **Error Boundaries:** Vue error handlers catch component failures - **Performance Monitoring:** Nuxt DevTools tracks component render times ## Dependencies and Integrations **External Dependencies:** - `@nuxt/fonts` (0.11.x): Inter font loading - `@nuxt/ui` (4.0.x): UButton, UIcon components - Tailwind CSS (4.1.x): Utility classes **Internal Dependencies:** - `app/types/resume.ts`: TypeScript interfaces (Epic 1) - `app/data/resume.en.ts`: Resume data (Epic 1) - `app/composables/useResumeData.ts`: Data access composable (Epic 1) **Integration Points:** - Epic 3: `/api/resume/pdf` will navigate to `/resume?print=true` - Future: Persian support will use `app/data/resume.fa.ts` ## Acceptance Criteria (Authoritative) ### AC1: Standalone Resume Route **Given** I navigate to `/resume` **When** the page loads **Then** I see the resume preview with no site header/footer **And** page title is "Resume - Ali Arghyani" **And** meta tag `` is present ### AC2: Single-Column Layout **Given** the resume page is rendered on desktop **When** I view the layout **Then** I see a single-column vertical stack with: - Header section at top (photo + name + contact) - Summary section - Work Experience section - Education section - Additional Information section at bottom **And** container has A4 aspect ratio (210mm × 297mm) **And** page margins are 24px (1.5rem) ### AC3: Responsive Behavior **Given** the resume page is rendered on mobile **When** viewport width < 768px **Then** layout remains single column but adapts spacing **And** profile photo may resize or reposition for mobile **And** section order remains the same ### AC4: Color Scheme and Typography **Given** the resume is displayed **When** I inspect the styling **Then** section headers are blue (#2563eb), uppercase, bold, with blue bottom border **And** background is white **And** text is dark gray (#1f2937) **And** font family is Inter **And** body text size is 14px (0.875rem) ### AC5: Header Section **Given** the header component renders **When** I view the top of the resume **Then** I see profile photo on the left (if provided) **And** name in large bold text (2rem) on the right **And** job title below name (1.25rem, gray) **And** contact details (address, phone, email, website) displayed with proper formatting **And** all contact links are clickable (mailto:, tel:, https://) ### AC6: Summary Section **Given** the summary component renders **When** I view after the header **Then** I see "SUMMARY" section header (blue, uppercase, underlined) **And** professional summary paragraph **And** line height is 1.6 for readability ### AC7: Experience Section **Given** the experience component renders **When** I view the work history **Then** I see "WORK EXPERIENCE" section header (blue, uppercase, underlined) **And** for each job: - Position title (bold, left-aligned) - Company name (normal weight) - Date range (right-aligned: "Jan 2022 - Present") - Bullet points for highlights (• character) **And** jobs are sorted by date (most recent first) **And** "Present" is shown for current jobs (no endDate) ### AC8: Education Section **Given** the education component renders **When** I view the education history **Then** I see "EDUCATION" section header (blue, uppercase, underlined) **And** for each degree: - Degree type and field (e.g., "Bachelor of Mechatronics Engineering with Honours") - Institution name - Date range (right-aligned: "Aug 2016 - Oct 2019") - Optional bullet points for achievements or coursework ### AC9: Additional Information Section **Given** the additional info component renders **When** I view the bottom section **Then** I see "ADDITIONAL INFORMATION" section header (blue, uppercase, underlined) **And** "Technical Skills:" with comma-separated or categorized list **And** "Languages:" with fluency levels (e.g., "English, Malay, Japan") **And** "Certifications:" (if present) with name and issuer **And** "Awards/Activities:" (if present) with brief descriptions ### AC10: Download Button **Given** I'm on the resume page **When** I view the page **Then** I see a floating action button in bottom-right corner **And** it has download icon (i-heroicons-arrow-down-tray) **And** it has "Download PDF" text **And** it has blue background color (#2563eb) **And** it has fixed position (doesn't scroll) **And** it has shadow for elevation **And** it has `.no-print` class ### AC11: Print Mode **Given** I navigate to `/resume?print=true` **When** the page loads **Then** the download button is not visible **And** all other content renders normally ### AC12: Print Styles **Given** the page is printed or captured by Puppeteer **When** print media query is active **Then** `.no-print` elements are hidden **And** colors print correctly (printBackground: true) **And** page breaks are controlled **And** A4 dimensions are maintained ### AC13: Section Header Styling **Given** any section header is rendered **When** I inspect its styling **Then** text is blue (#2563eb) **And** text is UPPERCASE **And** text is bold (font-weight: 700) **And** has a blue bottom border (border-bottom: 2px solid #2563eb) **And** has appropriate margin/padding for visual separation ### AC14: ATS Compatibility **Given** the HTML structure is inspected **When** I check semantic elements **Then** name uses `

` tag **And** section headers use `

` tags **And** no layout tables are used (CSS Grid/Flexbox only) **And** all text is real HTML text (not images) ## Traceability Mapping | AC | Spec Section | Components | Test Idea | |----|--------------|------------|-----------| | AC1 | Standalone Route | `pages/resume.vue` | Navigate to /resume, verify no nav, check meta tags | | AC2 | Single-Column Layout | `ResumePreview.vue` | Verify vertical stack order, measure container, verify A4 ratio | | AC3 | Responsive | `ResumePreview.vue` | Resize viewport, verify mobile adaptations | | AC4 | Color/Typography | All components | Inspect computed styles, verify colors and fonts | | AC5 | Header | `ResumeHeader.vue` | Check photo, name, title, contact info, click links | | AC6 | Summary | `ResumeSummary.vue` | Verify section header, paragraph, line height | | AC7 | Experience | `ResumeExperience.vue` | Check job entries, date formatting, bullet points | | AC8 | Education | `ResumeEducation.vue` | Verify degree, institution, date formatting | | AC9 | Additional Info | `ResumeAdditionalInfo.vue` | Check skills, languages, certs structure | | AC10 | Download Button | `ResumeDownloadButton.vue` | Verify FAB position, icon, styling, shadow | | AC11 | Print Mode | `pages/resume.vue` | Add ?print=true, verify button hidden | | AC12 | Print Styles | CSS | Trigger print preview, verify styles | | AC13 | Section Headers | All section components | Inspect header styling, verify blue, uppercase, border | | AC14 | ATS Compatibility | HTML structure | Inspect DOM, verify semantic tags, no tables | ## Risks, Assumptions, Open Questions **Risks:** - **Risk:** Font loading delay causes layout shift - **Mitigation:** Use @nuxt/fonts with preload, font-display: swap - **Risk:** Print styles differ across browsers - **Mitigation:** Test in Chrome, Firefox, Safari; use Puppeteer for PDF (consistent) - **Risk:** Long content causes page overflow - **Mitigation:** Test with max content length, add overflow handling - **Risk:** Profile image not provided or fails to load - **Mitigation:** Implement graceful fallback (hide image container, expand text area) **Assumptions:** - **Assumption:** Inter font is ATS-compatible - **Validation:** Inter is a standard web font, widely supported - **Assumption:** Single-column layout works for all content lengths - **Validation:** Test with varying experience entries (2-5 jobs), education (1-3 degrees) - **Assumption:** Blue (#2563eb) has sufficient contrast - **Validation:** WCAG AA contrast ratio verified (4.5:1 minimum) **Open Questions:** - **Question:** Should mobile view show download button? - **Answer:** Yes, but consider smaller size or icon-only - **Question:** How to handle very long job titles or company names? - **Answer:** Use text truncation with ellipsis, test with max lengths - **Question:** Profile photo aspect ratio and size? - **Answer:** Square or portrait (3:4), max 150px width, left-aligned in header ## Test Strategy Summary **Unit Tests:** - Each component renders with sample data - Date formatting helper works correctly - Composable returns expected data structure - Profile image fallback works when image unavailable **Integration Tests:** - Full page renders with all components in correct order - Print mode hides download button - Responsive layout adapts spacing on mobile **Visual Regression Tests:** - Screenshot comparison: web preview vs design template - Print preview matches web preview - Mobile layout matches design intent **Manual Tests:** - Navigate to /resume, verify all sections in correct order - Click all links (email, phone, website) - Test print preview (Ctrl+P) - Test on mobile device - Verify ATS compatibility (copy/paste text from PDF) - Test with and without profile photo **Performance Tests:** - Measure page load time (< 1s target) - Check LCP and CLS metrics - Verify font loading doesn't block render **Acceptance Tests:** - Run through all 14 ACs with real data - Verify pixel-perfect match to design template - Confirm Epic 3 can generate PDF from this page --- ## Revision History | Date | Author | Changes | |------|--------|---------| | 2025-11-30 | mahdi (initial) | Created initial tech spec (two-column layout) | | 2025-11-30 | Winston (Architect) | **MAJOR REVISION:** Changed to single-column layout per UX validation report. Refactored component architecture (merged Contact into Header, created AdditionalInfo, removed standalone Skills/Languages). Updated all ACs to match design template. | --- **Epic 2 Tech Spec - REVISED** **Status:** Ready for Review → Implementation **Next Step:** SM reviews revised spec, creates updated Story 2.1 draft