diff --git a/components/InteractiveViewer.tsx b/components/InteractiveViewer.tsx index ba6c65a49..0f29e9459 100644 --- a/components/InteractiveViewer.tsx +++ b/components/InteractiveViewer.tsx @@ -25,20 +25,23 @@ interface ParallaxParticle { * Deterministic math prevents random values from causing SSR/CSR mismatches. */ function buildParticles(): ParallaxParticle[] { const colors = ['#10b981', '#8b5cf6', '#06b6d4', '#3b82f6', '#f59e0b']; - return Array.from({ length: PARALLAX_PARTICLE_COUNT }, (_, i): ParallaxParticle => ({ - id: i, - // Spread particles across the container using prime-number strides - x: (i * 17 + 11) % 100, - y: (i * 23 + 7) % 100, - size: 4 + (i % 5) * 2, // range: 4–12 px - // Keep opacity low so particles never obscure the badge - opacity: 0.05 + (i % 4) * 0.025, // range: 0.05–0.125 - // Vary depth so each "layer" of particles shifts by a different amount, - // creating the illusion of 3-D depth. depth 0.1 = farthest; 0.7 = nearest. - depth: 0.1 + (i % 6) * 0.1, // range: 0.1–0.6 - color: colors[i % colors.length], - isCircle: i % 4 === 0, - })); + return Array.from( + { length: PARALLAX_PARTICLE_COUNT }, + (_, i): ParallaxParticle => ({ + id: i, + // Spread particles across the container using prime-number strides + x: (i * 17 + 11) % 100, + y: (i * 23 + 7) % 100, + size: 4 + (i % 5) * 2, // range: 4–12 px + // Keep opacity low so particles never obscure the badge + opacity: 0.05 + (i % 4) * 0.025, // range: 0.05–0.125 + // Vary depth so each "layer" of particles shifts by a different amount, + // creating the illusion of 3-D depth. depth 0.1 = farthest; 0.7 = nearest. + depth: 0.1 + (i % 6) * 0.1, // range: 0.1–0.6 + color: colors[i % colors.length], + isCircle: i % 4 === 0, + }) + ); } // How many pixels a depth-1.0 particle shifts when the cursor is at the @@ -399,29 +402,31 @@ export default function InteractiveViewer({ Each particle shifts by (parallaxX * depth, parallaxY * depth) px relative to its base position, so "closer" particles (higher depth) shift more — creating the impression of a multi-layered isometric space. */} - {particles.map((particle): ReactElement => ( -
- ))} + {particles.map( + (particle): ReactElement => ( + + ) + )} {/* ── Card content ────────────────────────────────────────────────────── diff --git a/components/burnout/BurnoutRiskTable.tsx b/components/burnout/BurnoutRiskTable.tsx index 4495276d9..c276cd649 100644 --- a/components/burnout/BurnoutRiskTable.tsx +++ b/components/burnout/BurnoutRiskTable.tsx @@ -34,7 +34,12 @@ interface BurnoutRiskTableProps { } type SortColumn = - 'username' | 'commitShare' | 'highIntensityWeeks' | 'restWeeks' | 'burnoutScore' | 'totalCommits'; + | 'username' + | 'commitShare' + | 'highIntensityWeeks' + | 'restWeeks' + | 'burnoutScore' + | 'totalCommits'; type SortDirection = 'asc' | 'desc'; // Custom Pure SVG Sparkline for visual performance diff --git a/lib/resume-parser.ts b/lib/resume-parser.ts index 202e498eb..e61e268ce 100644 --- a/lib/resume-parser.ts +++ b/lib/resume-parser.ts @@ -136,8 +136,9 @@ async function extractTextFromBuffer(buffer: Buffer, mimeType: string): Promise< let rawText = ''; if (mimeType === 'application/pdf') { - try { - if (buffer.toString('utf-8', 0, 4) === '%PDF') { + const header = buffer.toString('utf-8', 0, 4); + if (header === '%PDF') { + try { const { PDFParse } = await import('pdf-parse'); const parser = new PDFParse({ data: buffer }); @@ -145,36 +146,48 @@ async function extractTextFromBuffer(buffer: Buffer, mimeType: string): Promise< await parser.destroy(); rawText = result.text; - } else { - rawText = buffer.toString('utf-8'); + } catch (error) { + console.warn('Failed to parse PDF using pdf-parse:', error); + rawText = ''; } - } catch (error) { - console.warn('Failed to parse PDF using pdf-parse, falling back to UTF-8 decoding:', error); + } else { rawText = buffer.toString('utf-8'); } } else if ( mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ) { - try { - if (buffer.toString('utf-8', 0, 2) === 'PK') { + const header = buffer.toString('utf-8', 0, 2); + if (header === 'PK') { + try { const mammothModule = await import('mammoth'); const mammothParser = ((mammothModule as unknown as { default?: unknown }).default || mammothModule) as typeof mammothModule; const result = await mammothParser.extractRawText({ buffer }); rawText = result.value; - } else { - rawText = buffer.toString('utf-8'); + } catch (error) { + console.warn('Failed to parse DOCX using mammoth:', error); + rawText = ''; } - } catch (error) { - console.warn('Failed to parse DOCX using mammoth, falling back to UTF-8 decoding:', error); + } else { rawText = buffer.toString('utf-8'); } } else { rawText = buffer.toString('utf-8'); } + try { + if (rawText.includes('Ã')) { + const fixedText = Buffer.from(rawText, 'latin1').toString('utf-8'); + if (!fixedText.includes('\uFFFD')) { + rawText = fixedText; + } + } + } catch (_e) { + // Ignore encoding fix errors + } + const printable = rawText - .replace(/[^\x20-\x7E\n\r]/g, ' ') + .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFFFD]/g, '') .replace(/[ \t]+/g, ' ') .replace(/\r/g, '') .trim(); diff --git a/types/achievements.ts b/types/achievements.ts index b8c3d68c6..fd7272287 100644 --- a/types/achievements.ts +++ b/types/achievements.ts @@ -3,7 +3,11 @@ export type AchievementTier = 'bronze' | 'silver' | 'gold' | 'platinum' | 'diamo export type AchievementRarity = 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary' | 'mythic'; export type AchievementCategory = - 'contribution' | 'pull-request' | 'repository' | 'collaboration' | 'technology'; + | 'contribution' + | 'pull-request' + | 'repository' + | 'collaboration' + | 'technology'; export interface AchievementLevelDef { tier: AchievementTier;