Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 42 additions & 37 deletions components/InteractiveViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 => (
<div
key={particle.id}
style={{
position: 'absolute',
left: `${particle.x}%`,
top: `${particle.y}%`,
width: particle.size,
height: particle.size,
backgroundColor: particle.color,
borderRadius: particle.isCircle ? '50%' : '2px',
boxShadow: `0 0 ${particle.size * 2}px ${particle.color}55`,
opacity: isHovering ? particle.opacity * 1.8 : particle.opacity,
// Particles shift in the SAME direction as the cursor offset to create
// a realistic parallax: near objects (depth ~0.6) move more than far ones.
transform: `translate(${parallaxX * particle.depth}px, ${parallaxY * particle.depth}px)`,
// Smooth lerp toward the new position; opacity fades independently
transition: `transform 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94), opacity 0.5s ease`,
pointerEvents: 'none',
willChange: 'transform',
}}
/>
))}
{particles.map(
(particle): ReactElement => (
<div
key={particle.id}
style={{
position: 'absolute',
left: `${particle.x}%`,
top: `${particle.y}%`,
width: particle.size,
height: particle.size,
backgroundColor: particle.color,
borderRadius: particle.isCircle ? '50%' : '2px',
boxShadow: `0 0 ${particle.size * 2}px ${particle.color}55`,
opacity: isHovering ? particle.opacity * 1.8 : particle.opacity,
// Particles shift in the SAME direction as the cursor offset to create
// a realistic parallax: near objects (depth ~0.6) move more than far ones.
transform: `translate(${parallaxX * particle.depth}px, ${parallaxY * particle.depth}px)`,
// Smooth lerp toward the new position; opacity fades independently
transition: `transform 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94), opacity 0.5s ease`,
pointerEvents: 'none',
willChange: 'transform',
}}
/>
)
)}
</div>

{/* ── Card content ──────────────────────────────────────────────────────
Expand Down
1 change: 1 addition & 0 deletions components/burnout/BurnoutRiskTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import { useState, useMemo } from 'react';
import Image from 'next/image';
import { motion } from 'framer-motion';
import {
Flame,
Expand Down
39 changes: 25 additions & 14 deletions lib/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,13 +287,13 @@ export async function fetchWithRetry(
}

if (didThrow) {
if (options.signal?.aborted) throw fetchError;
if (options.signal?.aborted) throw new Error('AbortError');
Comment on lines 289 to +290
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));
Expand Down Expand Up @@ -908,13 +908,6 @@ export async function fetchGitHubContributions(

const loadWithTimeout = async (): Promise<ExtendedContributionData> => {
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<never>((_, reject) => {
Expand All @@ -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(() => {
Expand All @@ -957,7 +947,28 @@ export async function fetchGitHubContributions(
timer.unref();
}
}
return pending;

if (!options.signal) {
return pending;
}

return new Promise<ExtendedContributionData>((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) => {
Expand Down
6 changes: 5 additions & 1 deletion types/achievements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading