/ components / shared / PDFReportDocument.tsx
PDFReportDocument.tsx
   1  'use client';
   2  
   3  import {
   4    Document,
   5    Page,
   6    Text,
   7    View,
   8    StyleSheet,
   9    PDFDownloadLink,
  10    Font,
  11  } from '@react-pdf/renderer';
  12  import type { AnalysisResult, Gap, ActionItem, Strength, RoleRecommendation, MissingSkill, ATSKeyword, ATSFormatIssue, ATSRecommendation } from '@/lib/types';
  13  import type { GitHubAnalysis } from '@/lib/prompts/github-analysis';
  14  import type { CoverLetter } from '@/lib/prompts/cover-letter';
  15  
  16  // Register Poppins font — local TTF files with FULL Latin Extended charset
  17  // Poppins is a clean, modern Google Font that supports:
  18  // Romanian: ș (U+0219), ț (U+021B), ă (U+0103), â (U+00E2), î (U+00EE)
  19  // German: ä (U+00E4), ö (U+00F6), ü (U+00FC), ß (U+00DF)
  20  // Plus € (U+20AC) and all Latin Extended characters
  21  Font.register({
  22    family: 'Poppins',
  23    fonts: [
  24      { src: '/fonts/Poppins-Regular.ttf', fontWeight: 400 },
  25      { src: '/fonts/Poppins-Medium.ttf', fontWeight: 600 },
  26      { src: '/fonts/Poppins-Bold.ttf', fontWeight: 700 },
  27    ],
  28  });
  29  
  30  // Disable hyphenation to avoid font encoding issues
  31  Font.registerHyphenationCallback((word) => [word]);
  32  
  33  /**
  34   * Sanitize text for @react-pdf/renderer.
  35   * Poppins TTF supports most Latin Extended + € natively.
  36   * We only need to replace chars outside Poppins' glyph set (arrows, bullets, etc.)
  37   */
  38  function clean(text: string | undefined | null): string {
  39    if (!text) return '';
  40    return text
  41      .replace(/[\u2018\u2019\u201A]/g, "'")   // smart single quotes
  42      .replace(/[\u201C\u201D\u201E]/g, '"')    // smart double quotes
  43      .replace(/[\u2013\u2014]/g, '-')          // en/em dashes
  44      .replace(/\u2026/g, '...')                // ellipsis
  45      .replace(/\u00A3/g, 'GBP ')               // pound sign (not in Poppins)
  46      .replace(/\u00A5/g, 'JPY ')               // yen sign (not in Poppins)
  47      .replace(/\u2192/g, '->')                  // right arrow
  48      .replace(/\u2190/g, '<-')                  // left arrow
  49      .replace(/\u2191/g, '^')                   // up arrow
  50      .replace(/\u2193/g, 'v')                   // down arrow
  51      .replace(/\u2022/g, '-')                   // bullet
  52      .replace(/\u00B7/g, '-')                   // middle dot
  53      // Preserve Latin Extended + Euro (all supported by Poppins TTF)
  54      // Only strip characters outside these safe ranges
  55      .replace(/[^\x00-\x7F\u00C0-\u024F\u1E00-\u1EFF\u20AC]/g, '');
  56  }
  57  
  58  const colors = {
  59    bg: '#FFFBF5',
  60    card: '#FFFFFF',
  61    border: '#E8DDD2',
  62    primary: '#E8890A',
  63    success: '#10B981',
  64    warning: '#FBBF24',
  65    danger: '#EF4444',
  66    textPrimary: '#1C1410',
  67    textSecondary: '#6B5D52',
  68    white: '#FFFFFF',
  69  };
  70  
  71  const styles = StyleSheet.create({
  72    page: {
  73      backgroundColor: colors.bg,
  74      padding: 40,
  75      fontFamily: 'Poppins',
  76      color: colors.textPrimary,
  77      fontSize: 10,
  78    },
  79    // Header
  80    header: {
  81      marginBottom: 30,
  82      borderBottom: `1px solid ${colors.border}`,
  83      paddingBottom: 20,
  84    },
  85    logo: {
  86      fontSize: 18,
  87      fontWeight: 700,
  88      marginBottom: 4,
  89    },
  90    logoAccent: {
  91      color: colors.primary,
  92    },
  93    headerMeta: {
  94      fontSize: 9,
  95      color: colors.textSecondary,
  96      marginTop: 4,
  97    },
  98    // Section
  99    section: {
 100      marginBottom: 24,
 101    },
 102    sectionTitle: {
 103      fontSize: 14,
 104      fontWeight: 700,
 105      color: colors.textPrimary,
 106      marginBottom: 12,
 107      paddingBottom: 6,
 108      borderBottom: `1px solid ${colors.border}`,
 109    },
 110    // Fit Score
 111    fitScoreContainer: {
 112      backgroundColor: colors.card,
 113      borderRadius: 8,
 114      padding: 20,
 115      marginBottom: 24,
 116      alignItems: 'center',
 117      border: `1px solid ${colors.border}`,
 118    },
 119    fitScoreNumber: {
 120      fontSize: 36,
 121      fontWeight: 700,
 122      color: colors.primary,
 123    },
 124    fitScoreLabel: {
 125      fontSize: 12,
 126      fontWeight: 600,
 127      color: colors.textPrimary,
 128      marginTop: 4,
 129    },
 130    fitScoreSummary: {
 131      fontSize: 10,
 132      color: colors.textSecondary,
 133      marginTop: 10,
 134      textAlign: 'center',
 135      lineHeight: 1.5,
 136      maxWidth: 450,
 137    },
 138    // Cards
 139    card: {
 140      backgroundColor: colors.card,
 141      borderRadius: 6,
 142      padding: 12,
 143      marginBottom: 8,
 144      border: `1px solid ${colors.border}`,
 145    },
 146    cardTitle: {
 147      fontSize: 11,
 148      fontWeight: 600,
 149      color: colors.textPrimary,
 150      marginBottom: 4,
 151    },
 152    cardText: {
 153      fontSize: 9,
 154      color: colors.textSecondary,
 155      lineHeight: 1.5,
 156    },
 157    // Badges
 158    badge: {
 159      fontSize: 8,
 160      fontWeight: 600,
 161      paddingHorizontal: 6,
 162      paddingVertical: 2,
 163      borderRadius: 4,
 164      marginRight: 6,
 165    },
 166    badgeCritical: {
 167      backgroundColor: '#EF444420',
 168      color: colors.danger,
 169    },
 170    badgeModerate: {
 171      backgroundColor: '#EAB30820',
 172      color: colors.warning,
 173    },
 174    badgeMinor: {
 175      backgroundColor: '#10B98120',
 176      color: colors.success,
 177    },
 178    badgeDifferentiator: {
 179      backgroundColor: '#E8890A20',
 180      color: colors.primary,
 181    },
 182    badgeStrong: {
 183      backgroundColor: '#10B98120',
 184      color: colors.success,
 185    },
 186    badgeSupporting: {
 187      backgroundColor: '#E8DDD2',
 188      color: colors.textSecondary,
 189    },
 190    // Row layouts
 191    row: {
 192      flexDirection: 'row',
 193      alignItems: 'center',
 194      marginBottom: 4,
 195    },
 196    twoCol: {
 197      flexDirection: 'row',
 198      gap: 8,
 199    },
 200    col: {
 201      flex: 1,
 202    },
 203    // Salary bars
 204    salaryCard: {
 205      backgroundColor: colors.card,
 206      borderRadius: 6,
 207      padding: 12,
 208      border: `1px solid ${colors.border}`,
 209      flex: 1,
 210    },
 211    salaryLabel: {
 212      fontSize: 8,
 213      color: colors.textSecondary,
 214      marginBottom: 4,
 215    },
 216    salaryValue: {
 217      fontSize: 16,
 218      fontWeight: 700,
 219    },
 220    salaryRange: {
 221      fontSize: 8,
 222      color: colors.textSecondary,
 223      marginTop: 2,
 224    },
 225    // Action items
 226    actionItem: {
 227      backgroundColor: colors.card,
 228      borderRadius: 6,
 229      padding: 10,
 230      marginBottom: 6,
 231      border: `1px solid ${colors.border}`,
 232    },
 233    actionText: {
 234      fontSize: 10,
 235      fontWeight: 600,
 236      color: colors.textPrimary,
 237      marginBottom: 3,
 238    },
 239    actionMeta: {
 240      fontSize: 8,
 241      color: colors.textSecondary,
 242      lineHeight: 1.4,
 243    },
 244    impactText: {
 245      fontSize: 8,
 246      color: colors.success,
 247      marginTop: 3,
 248    },
 249    // Footer
 250    footer: {
 251      position: 'absolute',
 252      bottom: 25,
 253      left: 40,
 254      right: 40,
 255      flexDirection: 'row',
 256      justifyContent: 'space-between',
 257      fontSize: 8,
 258      color: colors.textSecondary,
 259      borderTop: `1px solid ${colors.border}`,
 260      paddingTop: 10,
 261    },
 262    // Utilities
 263    mb4: { marginBottom: 4 },
 264    mb8: { marginBottom: 8 },
 265    mb12: { marginBottom: 12 },
 266    mt8: { marginTop: 8 },
 267    textSmall: { fontSize: 8, color: colors.textSecondary },
 268    textPrimary: { color: colors.textPrimary },
 269    textSuccess: { color: colors.success },
 270    textDanger: { color: colors.danger },
 271    textWarning: { color: colors.warning },
 272    bold: { fontWeight: 600 },
 273  });
 274  
 275  function formatCurrency(amount: number, currency: string): string {
 276    return `${currency} ${amount.toLocaleString('en-US')}`;
 277  }
 278  
 279  function getSeverityBadge(severity: string) {
 280    const map: Record<string, typeof styles.badgeCritical> = {
 281      critical: styles.badgeCritical,
 282      moderate: styles.badgeModerate,
 283      minor: styles.badgeMinor,
 284    };
 285    return map[severity] || styles.badgeMinor;
 286  }
 287  
 288  function getTierBadge(tier: string) {
 289    const map: Record<string, typeof styles.badgeDifferentiator> = {
 290      differentiator: styles.badgeDifferentiator,
 291      strong: styles.badgeStrong,
 292      supporting: styles.badgeSupporting,
 293    };
 294    return map[tier] || styles.badgeSupporting;
 295  }
 296  
 297  // --- PDF Document ---
 298  
 299  export interface PDFLabels {
 300    brandLine: string;
 301    strengths: string;
 302    gaps: string;
 303    roles: string;
 304    salaryAnalysis: string;
 305    actionPlan30: string;
 306    actionPlan90: string;
 307    actionPlan12m: string;
 308    negotiationTips: string;
 309    current: string;
 310    required: string;
 311    growthPotential: string;
 312    currentRoleMarket: string;
 313    targetRoleMarket: string;
 314    companies: string;
 315    salarySubtitle: string;
 316    generatedOn: string;
 317    fitScoreLabel: string;
 318    dateLocale?: string;
 319    // New sections
 320    jobMatch: string;
 321    matchScore: string;
 322    matchingSkills: string;
 323    missingSkills: string;
 324    overallAdvice: string;
 325    cvSuggestions: string;
 326    suggested: string;
 327    reasoning: string;
 328    atsScore: string;
 329    keywordScore: string;
 330    formatScore: string;
 331    formatIssues: string;
 332    recommendations: string;
 333    githubAnalysis: string;
 334    projectIdea: string;
 335    improvements: string;
 336    whyRelevant: string;
 337    estimatedTime: string;
 338    coverLetter: string;
 339    tone: string;
 340    weaknessAcknowledgments: string;
 341    strengthHighlights: string;
 342  }
 343  
 344  const DEFAULT_LABELS: PDFLabels = {
 345    brandLine: 'GapZero - AI-Powered Career Advisor',
 346    strengths: 'Your Strengths',
 347    gaps: 'Skill Gaps',
 348    roles: 'Recommended Roles',
 349    salaryAnalysis: 'Salary Analysis',
 350    actionPlan30: '30-Day Quick Wins',
 351    actionPlan90: '90-Day Skill Building',
 352    actionPlan12m: '12-Month Career Trajectory',
 353    negotiationTips: 'Negotiation Tips',
 354    current: 'Current',
 355    required: 'Required',
 356    growthPotential: 'Growth Potential',
 357    currentRoleMarket: 'Current Role Market',
 358    targetRoleMarket: 'Target Role Market',
 359    companies: 'Companies',
 360    salarySubtitle: 'All figures are gross annual (before tax)',
 361    generatedOn: 'Generated on',
 362    fitScoreLabel: 'Career Fit Score',
 363    dateLocale: 'en-US',
 364    // New sections
 365    jobMatch: 'Job Match Analysis',
 366    matchScore: 'Match Score',
 367    matchingSkills: 'Matching Skills',
 368    missingSkills: 'Missing Keywords',
 369    overallAdvice: 'Overall Advice',
 370    cvSuggestions: 'CV Suggestions',
 371    suggested: 'Suggested',
 372    reasoning: 'Reasoning',
 373    atsScore: 'ATS Score Analysis',
 374    keywordScore: 'Keyword Score',
 375    formatScore: 'Format Score',
 376    formatIssues: 'Format Issues',
 377    recommendations: 'Recommendations',
 378    githubAnalysis: 'GitHub Analysis',
 379    projectIdea: 'Recommended Project',
 380    improvements: 'Areas to Improve',
 381    whyRelevant: 'Why This Helps',
 382    estimatedTime: 'Estimated Time',
 383    coverLetter: 'Cover Letter',
 384    tone: 'Tone',
 385    weaknessAcknowledgments: 'Weakness Acknowledgments',
 386    strengthHighlights: 'Strength Highlights',
 387  };
 388  
 389  // --- LinkedIn Plan computation (mirrors LinkedInPlan.tsx useMemo logic) ---
 390  
 391  function computeLinkedInPlan(result: AnalysisResult) {
 392    const { metadata, strengths, gaps, roleRecommendations, profile, githubAnalysis } = result;
 393    const target = metadata.targetRole;
 394    const sortedRoles = [...roleRecommendations].sort((a, b) => b.fitScore - a.fitScore);
 395    const topRole = sortedRoles[0];
 396  
 397    const differentiators = strengths.filter(s => s.tier === 'differentiator').map(s => s.title);
 398    const strongSkills = strengths.filter(s => s.tier === 'strong').map(s => s.title);
 399    const topSkillPhrase = differentiators.length > 0
 400      ? differentiators.slice(0, 2).join(' & ')
 401      : strongSkills.slice(0, 2).join(' & ');
 402  
 403    const headlines = [
 404      `${target} | ${topSkillPhrase} | ${differentiators[1] || strongSkills[2] || `Driving ${target} Impact`}`,
 405      `${target} -> ${topRole?.title || target} | ${differentiators[0] || strongSkills[0] || 'Tech Professional'} | Open to Opportunities`,
 406      `${topRole?.title || target} | ${metadata.country} (Remote) | ${topSkillPhrase}`,
 407    ];
 408  
 409    const currentTitle = profile?.currentRole || 'professional';
 410    const gapActions = gaps.filter(g => g.severity === 'critical').slice(0, 2).map(g => g.closingPlan);
 411    const about = `As a ${currentTitle} transitioning into ${target}, I bring ${topSkillPhrase} with a track record of delivering results.\n\nWhat I bring:\n${strengths.slice(0, 4).map(s => `- ${s.title}: ${s.description.split('.')[0]}.`).join('\n')}\n\nCurrently focused on:\n${gapActions.length > 0 ? gapActions.map(a => `- ${a.split('.')[0]}.`).join('\n') : `- Deepening expertise in ${target}.`}\n\nOpen to: ${target} roles${metadata.country ? ` | ${metadata.country} / Remote` : ''}`;
 412  
 413    const skillsToAdd: string[] = [];
 414    gaps.forEach(g => {
 415      if (g.severity === 'critical' || g.severity === 'moderate') skillsToAdd.push(g.skill);
 416    });
 417    skillsToAdd.push(target);
 418    if (topRole?.title && topRole.title !== target) skillsToAdd.push(topRole.title);
 419  
 420    const addSet = new Set(skillsToAdd.map(s => s.toLowerCase()));
 421    const skillsToRemove = strengths
 422      .filter(s => s.tier === 'supporting')
 423      .map(s => s.title)
 424      .filter(s => !addSet.has(s.toLowerCase()))
 425      .slice(0, 4);
 426  
 427    const profileSettings = [
 428      'Set "Open to Work" -> Recruiters Only (invisible to your current employer)',
 429      'Disable "Notify your network" during optimization (Settings -> Privacy)',
 430      'Enable Creator Mode if you plan to post - unlocks Follow button and post analytics',
 431      `Set your target location (${metadata.country || 'your target market'}) even if remote`,
 432      'Customize your LinkedIn URL to linkedin.com/in/yourname for a professional look',
 433    ];
 434  
 435    const contentIdeas = [
 436      `"I just built a ${topRole?.title || target} project. Here's what I learned and 3 things that surprised me."`,
 437      `"Week X of my ${gaps[0]?.skill || 'cloud certification'} journey. Key insight: [specific takeaway]."`,
 438      `"Hot take: [counterintuitive belief about ${target}]. Most people think X but I've seen Y."`,
 439      `"I went from [current domain] to ${target}. These 3 skills transferred perfectly - and these 2 didn't."`,
 440      `"1-2 min video: Walk through how you solved a specific ${target} problem. Raw and authentic wins."`,
 441    ];
 442  
 443    const connectionTargets = [
 444      ...(topRole?.exampleCompanies?.slice(0, 3) || []).map(c => `Recruiters and hiring managers at ${c}`),
 445      `Other ${target}s in ${metadata.country || 'your region'}`,
 446      `Content creators and thought leaders in the ${target} space`,
 447    ];
 448  
 449    const commentGroups = [
 450      { label: 'Peers in your field', reason: 'Relationship-building + staying visible to your immediate network' },
 451      { label: `Hiring managers & ${target} recruiters`, reason: 'Get on their radar before they post a job' },
 452      { label: 'Big influencers (1M+ followers)', reason: 'Algorithmic amplification - 3-5 thoughtful comments/week' },
 453      { label: 'Small creators (1K-10K followers)', reason: 'Community-building - higher response rates' },
 454    ];
 455  
 456    let portfolioItem: { title: string; tip: string };
 457    const gh = githubAnalysis as GitHubAnalysis | undefined;
 458    if (gh && gh.strengths.length > 0) {
 459      const topStrength = gh.strengths[0];
 460      portfolioItem = {
 461        title: topStrength.area,
 462        tip: `${topStrength.evidence} - Optimize the README with problem statement, tech decisions, demo link, and measurable results.`,
 463      };
 464    } else if (gh) {
 465      portfolioItem = {
 466        title: `Build: ${gh.projectIdea.name}`,
 467        tip: `${gh.projectIdea.description} Tech: ${gh.projectIdea.techStack.join(', ')}. ${gh.projectIdea.whyRelevant}`,
 468      };
 469    } else {
 470      portfolioItem = {
 471        title: `Portfolio Project: ${target}`,
 472        tip: `Build one focused project solving a real problem using ${topSkillPhrase}. Deploy it live with a clear README and measurable outcomes.`,
 473      };
 474    }
 475  
 476    const primaryGap = gaps.find(g => g.severity === 'critical') || gaps.find(g => g.severity === 'moderate');
 477    const articleTitle = primaryGap
 478      ? `"How I applied ${primaryGap.skill} to solve a real problem"`
 479      : `"${target} in ${new Date().getFullYear()}: what most people get wrong about ${topSkillPhrase}"`;
 480    const caseStudyItem = {
 481      title: 'Case Study or Article',
 482      tip: `${articleTitle} - Targets '${primaryGap?.skill || topSkillPhrase}' for recruiter search reach. Lead with a specific result. Aim for 600-900 words.`,
 483    };
 484  
 485    const certGap = gaps.find(g => g.severity === 'critical') || gaps.find(g => g.severity === 'moderate');
 486    const certItem = certGap
 487      ? {
 488          title: `Certification: ${certGap.skill}`,
 489          tip: `${certGap.closingPlan.split('.')[0]}. Pin the verified credential badge once earned - verification links get 40% more profile views.`,
 490        }
 491      : {
 492          title: `Advanced Certification in ${topSkillPhrase}`,
 493          tip: `An advanced credential in ${topSkillPhrase} signals seniority and differentiates you from other ${target} candidates.`,
 494        };
 495  
 496    const featuredItems = [portfolioItem, caseStudyItem, certItem];
 497  
 498    return { headlines, about, skillsToAdd, skillsToRemove, profileSettings, contentIdeas, connectionTargets, commentGroups, featuredItems };
 499  }
 500  
 501  // --- PDF Document ---
 502  
 503  function CareerReport({ result, labels: l }: { result: AnalysisResult; labels?: PDFLabels }) {
 504    const labels = l || DEFAULT_LABELS;
 505    const li = computeLinkedInPlan(result);
 506  
 507    return (
 508      <Document>
 509  
 510        {/* PAGE 1: Fit Score + Overall Advice + Job Match metrics */}
 511        <Page size="A4" style={styles.page}>
 512          {/* Header */}
 513          <View style={styles.header}>
 514            <Text style={styles.logo}>
 515              Gap<Text style={styles.logoAccent}>Zero</Text>
 516            </Text>
 517            <Text style={styles.headerMeta}>
 518              {clean(labels.fitScoreLabel)} - {clean(result.metadata.targetRole)} - {clean(result.metadata.country)}
 519            </Text>
 520            <Text style={styles.headerMeta}>
 521              {clean(labels.generatedOn)} {new Date(result.metadata.analyzedAt).toLocaleDateString(labels.dateLocale || 'en-US', {
 522                year: 'numeric', month: 'long', day: 'numeric',
 523              })} - CV: {clean(result.metadata.cvFileName)}
 524            </Text>
 525          </View>
 526  
 527          {/* Fit Score */}
 528          <View style={styles.fitScoreContainer}>
 529            <Text style={styles.fitScoreNumber}>{result.fitScore.score}/10</Text>
 530            <Text style={styles.fitScoreLabel}>{clean(result.fitScore.label)}</Text>
 531            <Text style={styles.fitScoreSummary}>{clean(result.fitScore.summary)}</Text>
 532          </View>
 533  
 534          {/* Overall Advice */}
 535          {result.jobMatch?.overallAdvice && (
 536            <View style={styles.section}>
 537              <Text style={styles.sectionTitle}>{clean(labels.overallAdvice)}</Text>
 538              <View style={styles.card}>
 539                <Text style={styles.cardText}>{clean(result.jobMatch.overallAdvice)}</Text>
 540              </View>
 541            </View>
 542          )}
 543  
 544          {/* Match Score */}
 545          {result.jobMatch && (
 546            <View style={[styles.fitScoreContainer, { marginBottom: 16 }]}>
 547              <Text style={styles.fitScoreNumber}>{result.jobMatch.matchScore}%</Text>
 548              <Text style={styles.fitScoreLabel}>{clean(labels.matchScore)}</Text>
 549            </View>
 550          )}
 551  
 552          {/* Matching Skills */}
 553          {result.jobMatch && result.jobMatch.matchingSkills.length > 0 && (
 554            <View style={styles.section}>
 555              <Text style={styles.sectionTitle}>{clean(labels.matchingSkills)}</Text>
 556              <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 4 }}>
 557                {result.jobMatch.matchingSkills.map((skill: string, i: number) => (
 558                  <Text key={i} style={[styles.badge, styles.badgeStrong]}>{clean(skill)}</Text>
 559                ))}
 560              </View>
 561            </View>
 562          )}
 563  
 564          {/* Missing Keywords */}
 565          {result.jobMatch && result.jobMatch.missingSkills.length > 0 && (
 566            <View style={styles.section}>
 567              <Text style={styles.sectionTitle}>{clean(labels.missingSkills)}</Text>
 568              {result.jobMatch.missingSkills.map((ms: MissingSkill, i: number) => (
 569                <View key={i} style={[styles.row, styles.mb4]}>
 570                  <Text style={[styles.badge, ms.importance === 'important' ? styles.badgeCritical : ms.importance === 'not_a_deal_breaker' ? styles.badgeModerate : styles.badgeMinor]}>
 571                    {ms.importance === 'important' ? 'REQUIRED' : ms.importance === 'not_a_deal_breaker' ? 'PREFERRED' : 'OPTIONAL'}
 572                  </Text>
 573                  <Text style={styles.cardText}>{clean(ms.skill)}</Text>
 574                </View>
 575              ))}
 576            </View>
 577          )}
 578  
 579          <View style={styles.footer}>
 580            <Text>{clean(labels.brandLine)}</Text>
 581            <Text>Fit Score</Text>
 582          </View>
 583        </Page>
 584  
 585        {/* PAGE 2: LinkedIn Plan */}
 586        <Page size="A4" style={styles.page}>
 587          <View style={styles.header}>
 588            <Text style={styles.logo}>Gap<Text style={styles.logoAccent}>Zero</Text></Text>
 589            <Text style={styles.headerMeta}>LinkedIn Profile Plan</Text>
 590          </View>
 591  
 592          {/* Headlines */}
 593          <View style={styles.section}>
 594            <Text style={styles.sectionTitle}>Headline Suggestions</Text>
 595            {li.headlines.map((h, i) => (
 596              <View key={i} style={styles.card}>
 597                <Text style={styles.cardTitle}>{i + 1}. {clean(h)}</Text>
 598              </View>
 599            ))}
 600          </View>
 601  
 602          {/* About Section */}
 603          <View style={styles.section}>
 604            <Text style={styles.sectionTitle}>About Section Draft</Text>
 605            <View style={[styles.card, { padding: 16 }]}>
 606              <Text style={styles.cardText}>{clean(li.about)}</Text>
 607            </View>
 608          </View>
 609  
 610          {/* Profile Settings */}
 611          <View style={styles.section}>
 612            <Text style={styles.sectionTitle}>Profile Settings</Text>
 613            {li.profileSettings.map((s, i) => (
 614              <View key={i} style={[styles.row, styles.mb4]}>
 615                <Text style={[styles.textSmall, { color: colors.success, marginRight: 6, flexShrink: 0 }]}>{i + 1}.</Text>
 616                <Text style={styles.cardText}>{clean(s)}</Text>
 617              </View>
 618            ))}
 619          </View>
 620  
 621          {/* Skills to Add */}
 622          <View style={styles.section}>
 623            <Text style={styles.sectionTitle}>Skills to Add</Text>
 624            <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 4 }}>
 625              {li.skillsToAdd.map((s, i) => (
 626                <Text key={i} style={[styles.badge, styles.badgeDifferentiator]}>{clean(s)}</Text>
 627              ))}
 628            </View>
 629          </View>
 630  
 631          {/* Skills to Deprioritize */}
 632          {li.skillsToRemove.length > 0 && (
 633            <View style={styles.section}>
 634              <Text style={styles.sectionTitle}>Skills to Deprioritize</Text>
 635              <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 4 }}>
 636                {li.skillsToRemove.map((s, i) => (
 637                  <Text key={i} style={[styles.badge, styles.badgeSupporting]}>{clean(s)}</Text>
 638                ))}
 639              </View>
 640            </View>
 641          )}
 642  
 643          {/* Featured Projects */}
 644          <View style={styles.section}>
 645            <Text style={styles.sectionTitle}>Featured Projects</Text>
 646            {li.featuredItems.map((item, i) => (
 647              <View key={i} style={styles.card}>
 648                <Text style={styles.cardTitle}>{clean(item.title)}</Text>
 649                <Text style={styles.cardText}>{clean(item.tip)}</Text>
 650              </View>
 651            ))}
 652          </View>
 653  
 654          {/* Content Ideas */}
 655          <View style={styles.section}>
 656            <Text style={styles.sectionTitle}>Content Ideas</Text>
 657            {li.contentIdeas.map((idea, i) => (
 658              <View key={i} style={[styles.card, styles.mb8]}>
 659                <View style={styles.row}>
 660                  <Text style={[styles.textSmall, { color: colors.primary, marginRight: 6, flexShrink: 0 }]}>{i + 1}.</Text>
 661                  <Text style={styles.cardText}>{clean(idea)}</Text>
 662                </View>
 663              </View>
 664            ))}
 665          </View>
 666  
 667          {/* Connection Strategy */}
 668          <View style={styles.section}>
 669            <Text style={styles.sectionTitle}>Connection Strategy</Text>
 670            {li.connectionTargets.map((t, i) => (
 671              <Text key={i} style={[styles.cardText, styles.mb4]}>- {clean(t)}</Text>
 672            ))}
 673          </View>
 674  
 675          {/* Commenting Groups */}
 676          <View style={styles.section}>
 677            <Text style={styles.sectionTitle}>Commenting Groups</Text>
 678            {li.commentGroups.map((g, i) => (
 679              <View key={i} style={[styles.card, styles.mb8]}>
 680                <Text style={styles.cardTitle}>{clean(g.label)}</Text>
 681                <Text style={styles.cardText}>{clean(g.reason)}</Text>
 682              </View>
 683            ))}
 684          </View>
 685  
 686          <View style={styles.footer}>
 687            <Text>{clean(labels.brandLine)}</Text>
 688            <Text>LinkedIn Plan</Text>
 689          </View>
 690        </Page>
 691  
 692        {/* PAGE 3 (conditional): ATS Analysis + CV Suggestions */}
 693        {result.atsScore && (
 694          <Page size="A4" style={styles.page}>
 695            <View style={styles.header}>
 696              <Text style={styles.logo}>Gap<Text style={styles.logoAccent}>Zero</Text></Text>
 697              <Text style={styles.headerMeta}>{clean(labels.atsScore)}</Text>
 698            </View>
 699  
 700            {/* Score cards */}
 701            <View style={[styles.twoCol, styles.mb12]}>
 702              <View style={[styles.salaryCard, { alignItems: 'center' }]}>
 703                <Text style={[styles.salaryValue, { color: colors.primary }]}>{result.atsScore.overallScore}</Text>
 704                <Text style={styles.salaryLabel}>Overall Score</Text>
 705              </View>
 706              <View style={[styles.salaryCard, { alignItems: 'center' }]}>
 707                <Text style={[styles.salaryValue, { color: colors.success }]}>{result.atsScore.keywordScore}</Text>
 708                <Text style={styles.salaryLabel}>{clean(labels.keywordScore)}</Text>
 709              </View>
 710              <View style={[styles.salaryCard, { alignItems: 'center' }]}>
 711                <Text style={[styles.salaryValue, { color: colors.textPrimary }]}>{result.atsScore.formatScore}</Text>
 712                <Text style={styles.salaryLabel}>{clean(labels.formatScore)}</Text>
 713              </View>
 714            </View>
 715  
 716            {/* Matched keywords */}
 717            {result.atsScore.keywords.matched.length > 0 && (
 718              <View style={styles.section}>
 719                <Text style={styles.sectionTitle}>{clean(labels.matchingSkills)}</Text>
 720                <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 4 }}>
 721                  {result.atsScore.keywords.matched.map((kw: ATSKeyword, i: number) => (
 722                    <Text key={i} style={[styles.badge, styles.badgeStrong]}>{clean(kw.keyword)}</Text>
 723                  ))}
 724                </View>
 725              </View>
 726            )}
 727  
 728            {/* Missing keywords */}
 729            {result.atsScore.keywords.missing.length > 0 && (
 730              <View style={styles.section}>
 731                <Text style={styles.sectionTitle}>{clean(labels.missingSkills)}</Text>
 732                <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 4 }}>
 733                  {result.atsScore.keywords.missing.map((kw: ATSKeyword, i: number) => (
 734                    <Text key={i} style={[styles.badge, kw.importance === 'high' ? styles.badgeCritical : kw.importance === 'medium' ? styles.badgeModerate : styles.badgeMinor]}>
 735                      {clean(kw.keyword)}
 736                    </Text>
 737                  ))}
 738                </View>
 739              </View>
 740            )}
 741  
 742            {/* Format issues */}
 743            {result.atsScore.formatIssues.length > 0 && (
 744              <View style={styles.section}>
 745                <Text style={styles.sectionTitle}>{clean(labels.formatIssues)}</Text>
 746                {result.atsScore.formatIssues.map((issue: ATSFormatIssue, i: number) => (
 747                  <View key={i} style={[styles.card, styles.mb8]}>
 748                    <View style={styles.row}>
 749                      <Text style={[styles.badge, getSeverityBadge(issue.severity === 'critical' ? 'critical' : issue.severity === 'warning' ? 'moderate' : 'minor')]}>
 750                        {issue.severity.toUpperCase()}
 751                      </Text>
 752                      <Text style={styles.cardTitle}>{clean(issue.issue)}</Text>
 753                    </View>
 754                    <Text style={styles.cardText}>{clean(issue.description)}</Text>
 755                    <Text style={[styles.cardText, { color: colors.primary, marginTop: 4 }]}>{clean(issue.fix)}</Text>
 756                  </View>
 757                ))}
 758              </View>
 759            )}
 760  
 761            {/* Recommendations */}
 762            {result.atsScore.recommendations.length > 0 && (
 763              <View style={styles.section}>
 764                <Text style={styles.sectionTitle}>{clean(labels.recommendations)}</Text>
 765                {result.atsScore.recommendations.map((rec: ATSRecommendation, i: number) => (
 766                  <View key={i} style={[styles.card, styles.mb8]}>
 767                    <View style={styles.row}>
 768                      <Text style={[styles.badge, getSeverityBadge(rec.priority === 'critical' ? 'critical' : rec.priority === 'high' ? 'moderate' : 'minor')]}>
 769                        {rec.priority.toUpperCase()}
 770                      </Text>
 771                      <Text style={styles.cardTitle}>{clean(rec.section)}</Text>
 772                    </View>
 773                    <Text style={styles.cardText}>{clean(rec.action)}</Text>
 774                    {rec.example && (
 775                      <Text style={[styles.cardText, { color: colors.primary, marginTop: 4 }]}>{clean(rec.example)}</Text>
 776                    )}
 777                  </View>
 778                ))}
 779              </View>
 780            )}
 781  
 782            {/* CV Suggestions (from Job Match) */}
 783            {result.jobMatch && result.jobMatch.cvSuggestions.length > 0 && (
 784              <View style={styles.section}>
 785                <Text style={styles.sectionTitle}>{clean(labels.cvSuggestions)}</Text>
 786                {result.jobMatch.cvSuggestions.map((s, i) => (
 787                  <View key={i} style={[styles.card, styles.mb8]}>
 788                    <Text style={[styles.textSmall, styles.bold, { color: colors.primary, marginBottom: 4 }]}>{clean(s.section)}</Text>
 789                    <Text style={[styles.textSmall, styles.bold]}>{clean(labels.current)}:</Text>
 790                    <Text style={[styles.cardText, { marginBottom: 4 }]}>{clean(s.current)}</Text>
 791                    <Text style={[styles.textSmall, styles.bold]}>{clean(labels.suggested)}:</Text>
 792                    <Text style={[styles.cardText, { color: colors.success, marginBottom: 4 }]}>{clean(s.suggested)}</Text>
 793                    <Text style={[styles.textSmall, { color: colors.textSecondary }]}>{clean(s.reasoning)}</Text>
 794                  </View>
 795                ))}
 796              </View>
 797            )}
 798  
 799            <View style={styles.footer}>
 800              <Text>{clean(labels.brandLine)}</Text>
 801              <Text>ATS Score</Text>
 802            </View>
 803          </Page>
 804        )}
 805  
 806        {/* PAGE 4 (conditional): Cover Letter */}
 807        {result.coverLetter && (
 808          <Page size="A4" style={styles.page}>
 809            <View style={styles.header}>
 810              <Text style={styles.logo}>Gap<Text style={styles.logoAccent}>Zero</Text></Text>
 811              <Text style={styles.headerMeta}>{clean(labels.coverLetter)}</Text>
 812            </View>
 813  
 814            <View style={styles.section}>
 815              {/* Letter body */}
 816              <View style={[styles.card, { padding: 20 }]}>
 817                <Text style={[styles.cardText, styles.mb8]}>{clean((result.coverLetter as CoverLetter).greeting)}</Text>
 818                <Text style={[styles.cardText, styles.mb8]}>{clean((result.coverLetter as CoverLetter).openingParagraph)}</Text>
 819                {(result.coverLetter as CoverLetter).bodyParagraphs.map((para: string, i: number) => (
 820                  <Text key={i} style={[styles.cardText, styles.mb8]}>{clean(para)}</Text>
 821                ))}
 822                <Text style={[styles.cardText, styles.mb8]}>{clean((result.coverLetter as CoverLetter).closingParagraph)}</Text>
 823                <Text style={[styles.cardText, styles.bold]}>{clean((result.coverLetter as CoverLetter).signature)}</Text>
 824              </View>
 825            </View>
 826  
 827            {/* Tone */}
 828            <View style={[styles.twoCol, styles.mb8]}>
 829              <View style={styles.col}>
 830                <View style={styles.card}>
 831                  <Text style={[styles.textSmall, styles.bold, styles.mb4]}>{clean(labels.tone)}: {clean((result.coverLetter as CoverLetter).toneUsed)}</Text>
 832                </View>
 833              </View>
 834            </View>
 835  
 836            {/* Strength highlights */}
 837            {(result.coverLetter as CoverLetter).strengthHighlights.length > 0 && (
 838              <View style={styles.section}>
 839                <Text style={styles.sectionTitle}>{clean(labels.strengthHighlights)}</Text>
 840                {(result.coverLetter as CoverLetter).strengthHighlights.map((h: string, i: number) => (
 841                  <Text key={i} style={[styles.cardText, styles.mb4]}>- {clean(h)}</Text>
 842                ))}
 843              </View>
 844            )}
 845  
 846            {/* Weakness acknowledgments */}
 847            {(result.coverLetter as CoverLetter).weaknessAcknowledgments.length > 0 && (
 848              <View style={styles.section}>
 849                <Text style={styles.sectionTitle}>{clean(labels.weaknessAcknowledgments)}</Text>
 850                {(result.coverLetter as CoverLetter).weaknessAcknowledgments.map((w: string, i: number) => (
 851                  <Text key={i} style={[styles.cardText, styles.mb4]}>- {clean(w)}</Text>
 852                ))}
 853              </View>
 854            )}
 855  
 856            <View style={styles.footer}>
 857              <Text>{clean(labels.brandLine)}</Text>
 858              <Text>Cover Letter</Text>
 859            </View>
 860          </Page>
 861        )}
 862  
 863        {/* PAGE 5 (conditional): GitHub Analysis — Project first, then Strengths, then Improvements */}
 864        {result.githubAnalysis && (
 865          <Page size="A4" style={styles.page}>
 866            <View style={styles.header}>
 867              <Text style={styles.logo}>Gap<Text style={styles.logoAccent}>Zero</Text></Text>
 868              <Text style={styles.headerMeta}>{clean(labels.githubAnalysis)}</Text>
 869            </View>
 870  
 871            {/* Stats */}
 872            <View style={[styles.twoCol, styles.mb12]}>
 873              <View style={[styles.salaryCard, { alignItems: 'center' }]}>
 874                <Text style={styles.salaryValue}>{result.githubAnalysis.stats.totalRepos}</Text>
 875                <Text style={styles.salaryLabel}>Repos</Text>
 876              </View>
 877              <View style={[styles.salaryCard, { alignItems: 'center' }]}>
 878                <Text style={[styles.salaryValue, { fontSize: 11 }]}>{result.githubAnalysis.stats.topLanguages.slice(0, 3).join(', ')}</Text>
 879                <Text style={styles.salaryLabel}>Top Languages</Text>
 880              </View>
 881              <View style={[styles.salaryCard, { alignItems: 'center' }]}>
 882                <Text style={styles.salaryValue}>{result.githubAnalysis.stats.avgStars.toFixed(1)}</Text>
 883                <Text style={styles.salaryLabel}>Avg Stars</Text>
 884              </View>
 885            </View>
 886  
 887            {/* Recommended Project (first) */}
 888            {result.githubAnalysis.projectIdea?.name && (
 889              <View style={styles.section}>
 890                <Text style={styles.sectionTitle}>{clean(labels.projectIdea)}</Text>
 891                <View style={[styles.card, { borderColor: colors.primary, borderWidth: 1.5 }]}>
 892                  <Text style={[styles.cardTitle, { color: colors.primary, fontSize: 13 }]}>{clean(result.githubAnalysis.projectIdea.name)}</Text>
 893                  <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 4, marginVertical: 6 }}>
 894                    {result.githubAnalysis.projectIdea.techStack.map((tech, i) => (
 895                      <Text key={i} style={[styles.badge, styles.badgeDifferentiator]}>{clean(tech)}</Text>
 896                    ))}
 897                  </View>
 898                  <Text style={styles.cardText}>{clean(result.githubAnalysis.projectIdea.description)}</Text>
 899                  <Text style={[styles.cardText, { color: colors.success, marginTop: 4 }]}>
 900                    {clean(labels.whyRelevant)}: {clean(result.githubAnalysis.projectIdea.whyRelevant)}
 901                  </Text>
 902                  <Text style={[styles.textSmall, { marginTop: 4 }]}>
 903                    {clean(labels.estimatedTime)}: {clean(result.githubAnalysis.projectIdea.estimatedTime)}
 904                  </Text>
 905                </View>
 906              </View>
 907            )}
 908  
 909            {/* Strengths */}
 910            {result.githubAnalysis.strengths.length > 0 && (
 911              <View style={styles.section}>
 912                <Text style={styles.sectionTitle}>{clean(labels.strengths)}</Text>
 913                {(result.githubAnalysis as GitHubAnalysis).strengths.map((s, i) => (
 914                  <View key={i} style={styles.card}>
 915                    <Text style={styles.cardTitle}>{clean(s.area)}</Text>
 916                    <Text style={styles.cardText}>{clean(s.description)}</Text>
 917                    <Text style={[styles.textSmall, { color: colors.success, marginTop: 4 }]}>{clean(s.evidence)}</Text>
 918                  </View>
 919                ))}
 920              </View>
 921            )}
 922  
 923            {/* Areas to Improve */}
 924            {result.githubAnalysis.improvements.length > 0 && (
 925              <View style={styles.section}>
 926                <Text style={styles.sectionTitle}>{clean(labels.improvements)}</Text>
 927                {(result.githubAnalysis as GitHubAnalysis).improvements.map((imp, i) => (
 928                  <View key={i} style={[styles.card, styles.mb8]}>
 929                    <View style={styles.row}>
 930                      <Text style={[styles.badge, getSeverityBadge(imp.priority === 'high' ? 'critical' : imp.priority === 'medium' ? 'moderate' : 'minor')]}>
 931                        {imp.priority.toUpperCase()}
 932                      </Text>
 933                      <Text style={styles.cardTitle}>{clean(imp.area)}</Text>
 934                    </View>
 935                    <Text style={styles.cardText}>{clean(imp.description)}</Text>
 936                    <Text style={[styles.cardText, { color: colors.primary, marginTop: 4 }]}>{clean(imp.actionable)}</Text>
 937                  </View>
 938                ))}
 939              </View>
 940            )}
 941  
 942            <View style={styles.footer}>
 943              <Text>{clean(labels.brandLine)}</Text>
 944              <Text>GitHub</Text>
 945            </View>
 946          </Page>
 947        )}
 948  
 949        {/* PAGE 6: Strengths + Gaps */}
 950        <Page size="A4" style={styles.page}>
 951          {/* Strengths */}
 952          <View style={styles.section}>
 953            <Text style={styles.sectionTitle}>{clean(labels.strengths)}</Text>
 954            {result.strengths.map((str: Strength, i: number) => (
 955              <View key={i} style={styles.card}>
 956                <View style={styles.row}>
 957                  <Text style={[styles.badge, getTierBadge(str.tier)]}>{str.tier.toUpperCase()}</Text>
 958                  <Text style={styles.cardTitle}>{clean(str.title)}</Text>
 959                </View>
 960                <Text style={styles.cardText}>{clean(str.description)}</Text>
 961                <Text style={[styles.cardText, { color: colors.primary, marginTop: 4 }]}>{clean(str.relevance)}</Text>
 962              </View>
 963            ))}
 964          </View>
 965  
 966          {/* Gaps */}
 967          <View style={styles.section}>
 968            <Text style={styles.sectionTitle}>{clean(labels.gaps)}</Text>
 969            {result.gaps.map((g: Gap, i: number) => (
 970              <View key={i} style={styles.card}>
 971                <View style={styles.row}>
 972                  <Text style={[styles.badge, getSeverityBadge(g.severity)]}>{g.severity.toUpperCase()}</Text>
 973                  <Text style={styles.cardTitle}>{clean(g.skill)}</Text>
 974                  <Text style={[styles.textSmall, { marginLeft: 'auto' }]}>{clean(g.timeToClose)}</Text>
 975                </View>
 976                <Text style={[styles.cardText, { color: colors.danger }]}>{clean(g.impact)}</Text>
 977                <View style={[styles.twoCol, styles.mt8]}>
 978                  <View style={styles.col}>
 979                    <Text style={[styles.textSmall, styles.bold]}>{clean(labels.current)}</Text>
 980                    <Text style={styles.cardText}>{clean(g.currentLevel)}</Text>
 981                  </View>
 982                  <View style={styles.col}>
 983                    <Text style={[styles.textSmall, styles.bold]}>{clean(labels.required)}</Text>
 984                    <Text style={styles.cardText}>{clean(g.requiredLevel)}</Text>
 985                  </View>
 986                </View>
 987                <Text style={[styles.cardText, { color: colors.primary, marginTop: 6 }]}>{clean(g.closingPlan)}</Text>
 988                {g.resources.length > 0 && (
 989                  <View style={styles.mt8}>
 990                    {g.resources.map((res: string, ri: number) => (
 991                      <Text key={ri} style={[styles.textSmall, styles.mb4]}>- {clean(res)}</Text>
 992                    ))}
 993                  </View>
 994                )}
 995              </View>
 996            ))}
 997          </View>
 998  
 999          <View style={styles.footer}>
1000            <Text>{clean(labels.brandLine)}</Text>
1001            <Text>Strengths & Gaps</Text>
1002          </View>
1003        </Page>
1004  
1005        {/* PAGE 7: Action Plan (30-day + 90-day + 12-month) */}
1006        <Page size="A4" style={styles.page}>
1007          <View style={styles.section}>
1008            <Text style={styles.sectionTitle}>{clean(labels.actionPlan30)}</Text>
1009            {result.actionPlan.thirtyDays.map((item: ActionItem, i: number) => (
1010              <View key={i} style={styles.actionItem}>
1011                <View style={styles.row}>
1012                  <Text style={[styles.badge, getSeverityBadge(item.priority === 'high' ? 'moderate' : item.priority === 'medium' ? 'minor' : 'critical')]}>
1013                    {item.priority.toUpperCase()}
1014                  </Text>
1015                  <Text style={styles.textSmall}>{clean(item.timeEstimate)}</Text>
1016                </View>
1017                <Text style={styles.actionText}>{clean(item.action)}</Text>
1018                <Text style={styles.actionMeta}>{clean(item.resource)}</Text>
1019                <Text style={styles.impactText}>{clean(item.expectedImpact)}</Text>
1020              </View>
1021            ))}
1022          </View>
1023  
1024          <View style={styles.section}>
1025            <Text style={styles.sectionTitle}>{clean(labels.actionPlan90)}</Text>
1026            {result.actionPlan.ninetyDays.map((item: ActionItem, i: number) => (
1027              <View key={i} style={styles.actionItem}>
1028                <View style={styles.row}>
1029                  <Text style={[styles.badge, getSeverityBadge(item.priority === 'high' ? 'moderate' : item.priority === 'medium' ? 'minor' : 'critical')]}>
1030                    {item.priority.toUpperCase()}
1031                  </Text>
1032                  <Text style={styles.textSmall}>{clean(item.timeEstimate)}</Text>
1033                </View>
1034                <Text style={styles.actionText}>{clean(item.action)}</Text>
1035                <Text style={styles.actionMeta}>{clean(item.resource)}</Text>
1036                <Text style={styles.impactText}>{clean(item.expectedImpact)}</Text>
1037              </View>
1038            ))}
1039          </View>
1040  
1041          <View style={styles.section}>
1042            <Text style={styles.sectionTitle}>{clean(labels.actionPlan12m)}</Text>
1043            {result.actionPlan.twelveMonths.map((item: ActionItem, i: number) => (
1044              <View key={i} style={styles.actionItem}>
1045                <View style={styles.row}>
1046                  <Text style={[styles.badge, getSeverityBadge(item.priority === 'high' ? 'moderate' : item.priority === 'medium' ? 'minor' : 'critical')]}>
1047                    {item.priority.toUpperCase()}
1048                  </Text>
1049                  <Text style={styles.textSmall}>{clean(item.timeEstimate)}</Text>
1050                </View>
1051                <Text style={styles.actionText}>{clean(item.action)}</Text>
1052                <Text style={styles.actionMeta}>{clean(item.resource)}</Text>
1053                <Text style={styles.impactText}>{clean(item.expectedImpact)}</Text>
1054              </View>
1055            ))}
1056          </View>
1057  
1058          <View style={styles.footer}>
1059            <Text>{clean(labels.brandLine)}</Text>
1060            <Text>Action Plan</Text>
1061          </View>
1062        </Page>
1063  
1064        {/* PAGE 8: Recommended Roles + Salary Analysis + Negotiation Tips */}
1065        <Page size="A4" style={styles.page}>
1066          {/* Roles */}
1067          <View style={styles.section}>
1068            <Text style={styles.sectionTitle}>{clean(labels.roles)}</Text>
1069            {[...result.roleRecommendations].sort((a, b) => b.fitScore - a.fitScore).map((role: RoleRecommendation, i: number) => (
1070              <View key={i} style={styles.card}>
1071                <View style={styles.row}>
1072                  <Text style={[styles.badge, styles.badgeDifferentiator]}>{role.fitScore}/10</Text>
1073                  <Text style={styles.cardTitle}>{clean(role.title)}</Text>
1074                </View>
1075                <Text style={styles.cardText}>{clean(role.reasoning)}</Text>
1076                <View style={[styles.row, styles.mt8]}>
1077                  <Text style={[styles.textSmall, styles.textSuccess]}>
1078                    {formatCurrency(role.salaryRange.low, role.salaryRange.currency)} - {formatCurrency(role.salaryRange.high, role.salaryRange.currency)}
1079                  </Text>
1080                  <Text style={[styles.textSmall, { marginLeft: 12 }]}>{clean(role.timeToReady)}</Text>
1081                </View>
1082                <Text style={[styles.textSmall, styles.mt8]}>
1083                  {clean(labels.companies)}: {role.exampleCompanies.map(c => clean(c)).join(', ')}
1084                </Text>
1085              </View>
1086            ))}
1087          </View>
1088  
1089          {/* Salary Analysis */}
1090          <View style={styles.section}>
1091            <Text style={styles.sectionTitle}>{clean(labels.salaryAnalysis)}</Text>
1092            <Text style={[styles.textSmall, styles.mb8]}>{clean(labels.salarySubtitle)}</Text>
1093            <View style={[styles.twoCol, styles.mb8]}>
1094              <View style={styles.salaryCard}>
1095                <Text style={styles.salaryLabel}>{clean(labels.currentRoleMarket)}</Text>
1096                <Text style={[styles.salaryValue, styles.textPrimary]}>
1097                  {formatCurrency(result.salaryAnalysis.currentRoleMarket.mid, result.salaryAnalysis.currentRoleMarket.currency)}
1098                </Text>
1099                <Text style={styles.salaryRange}>
1100                  {formatCurrency(result.salaryAnalysis.currentRoleMarket.low, result.salaryAnalysis.currentRoleMarket.currency)} - {formatCurrency(result.salaryAnalysis.currentRoleMarket.high, result.salaryAnalysis.currentRoleMarket.currency)}
1101                </Text>
1102                <Text style={[styles.textSmall, styles.mt8]}>{clean(result.salaryAnalysis.currentRoleMarket.region)}</Text>
1103              </View>
1104              <View style={styles.salaryCard}>
1105                <Text style={styles.salaryLabel}>{clean(labels.targetRoleMarket)}</Text>
1106                <Text style={[styles.salaryValue, styles.textSuccess]}>
1107                  {formatCurrency(result.salaryAnalysis.targetRoleMarket.mid, result.salaryAnalysis.targetRoleMarket.currency)}
1108                </Text>
1109                <Text style={styles.salaryRange}>
1110                  {formatCurrency(result.salaryAnalysis.targetRoleMarket.low, result.salaryAnalysis.targetRoleMarket.currency)} - {formatCurrency(result.salaryAnalysis.targetRoleMarket.high, result.salaryAnalysis.targetRoleMarket.currency)}
1111                </Text>
1112                <Text style={[styles.textSmall, styles.mt8]}>{clean(result.salaryAnalysis.targetRoleMarket.region)}</Text>
1113              </View>
1114            </View>
1115            <View style={styles.card}>
1116              <Text style={[styles.cardText, styles.textSuccess, styles.bold]}>{clean(labels.growthPotential)}: {clean(result.salaryAnalysis.growthPotential)}</Text>
1117              <Text style={[styles.cardText, styles.mt8]}>{clean(result.salaryAnalysis.bestMonetaryMove)}</Text>
1118            </View>
1119          </View>
1120  
1121          {/* Negotiation Tips */}
1122          {result.salaryAnalysis.negotiationTips.length > 0 && (
1123            <View style={styles.section}>
1124              <Text style={styles.sectionTitle}>{clean(labels.negotiationTips)}</Text>
1125              {result.salaryAnalysis.negotiationTips.map((tip: string, i: number) => (
1126                <View key={i} style={[styles.card, styles.mb8]}>
1127                  <Text style={styles.cardText}>
1128                    <Text style={[styles.bold, { color: colors.primary }]}>{i + 1}. </Text>
1129                    {clean(tip)}
1130                  </Text>
1131                </View>
1132              ))}
1133            </View>
1134          )}
1135  
1136          <View style={styles.footer}>
1137            <Text>{clean(labels.brandLine)}</Text>
1138            <Text>Roles & Salary</Text>
1139          </View>
1140        </Page>
1141  
1142      </Document>
1143    );
1144  }
1145  
1146  // --- Download Button ---
1147  export function PDFDownloadButton({ result, buttonLabel, labels }: { result: AnalysisResult; buttonLabel?: string; labels?: PDFLabels }) {
1148    const filename = `GapZero_Analysis_${result.metadata.targetRole.replace(/\s+/g, '_')}_${new Date().toISOString().split('T')[0]}.pdf`;
1149  
1150    return (
1151      <PDFDownloadLink
1152        document={<CareerReport result={result} labels={labels} />}
1153        fileName={filename}
1154      >
1155        {({ loading }) => (
1156          <button
1157            disabled={loading}
1158            className="btn-primary text-sm !py-2.5 !px-5 !rounded-xl flex items-center gap-2"
1159          >
1160            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
1161              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
1162              <polyline points="7 10 12 15 17 10" />
1163              <line x1="12" y1="15" x2="12" y2="3" />
1164            </svg>
1165            {loading ? '...' : (buttonLabel || 'Download Report')}
1166          </button>
1167        )}
1168      </PDFDownloadLink>
1169    );
1170  }