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 7cf69d2c5..c276cd649 100644 --- a/components/burnout/BurnoutRiskTable.tsx +++ b/components/burnout/BurnoutRiskTable.tsx @@ -1,6 +1,7 @@ 'use client'; import { useState, useMemo } from 'react'; +import Image from 'next/image'; import { motion } from 'framer-motion'; import { Flame, diff --git a/lib/github.ts b/lib/github.ts index ebba54c16..d8d796ccd 100644 --- a/lib/github.ts +++ b/lib/github.ts @@ -287,13 +287,13 @@ export async function fetchWithRetry( } if (didThrow) { - if (options.signal?.aborted) throw fetchError; + if (options.signal?.aborted) throw new Error('AbortError'); const isTimeoutAbort = isAbortError(fetchError); if (attempt >= MAX_RETRIES) { if (isTimeoutAbort) { throw new Error(`GitHub API request timed out after ${resolvedTimeout / 1000}s`); } - throw fetchError; + throw new Error(fetchError instanceof Error ? fetchError.message : String(fetchError)); } const delay = getJitteredBackoff(attempt); await new Promise((resolve) => setTimeout(resolve, delay)); @@ -908,13 +908,6 @@ export async function fetchGitHubContributions( const loadWithTimeout = async (): Promise => { const controller = new AbortController(); - if (options.signal) { - if (options.signal.aborted) { - controller.abort(); - } else { - options.signal.addEventListener('abort', () => controller.abort(), { once: true }); - } - } let timerId = null; const timeoutPromise = new Promise((_, reject) => { @@ -939,10 +932,7 @@ export async function fetchGitHubContributions( } }; - const coalescedLoad = () => { - if (options.signal) { - return loadWithTimeout(); - } + const coalescedLoad = async () => { let pending = activeContributionsPromises.get(key); if (!pending) { pending = loadWithTimeout().finally(() => { @@ -957,7 +947,28 @@ export async function fetchGitHubContributions( timer.unref(); } } - return pending; + + if (!options.signal) { + return pending; + } + + return new Promise((resolve, reject) => { + if (options.signal?.aborted) return reject(new Error('AbortError')); + + const onAbort = () => reject(new Error('AbortError')); + options.signal?.addEventListener('abort', onAbort, { once: true }); + + pending!.then( + (val) => { + options.signal?.removeEventListener('abort', onAbort); + resolve(val); + }, + (err) => { + options.signal?.removeEventListener('abort', onAbort); + reject(err); + } + ); + }); }; const revalidateInBackground = (username: string, cacheKey: string) => { 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;