From 5c5b4f5823425e4190252c43d4800e12f6edeb88 Mon Sep 17 00:00:00 2001 From: ArshVermaGit Date: Sat, 30 May 2026 02:28:04 +0530 Subject: [PATCH 1/2] feat(ui): add micro-animations & transitions for dynamic dashboard UX --- src/app/globals.css | 53 +++++++++++++++++++ src/components/AIMentorWidget.tsx | 6 +-- src/components/ActivityRingChart.tsx | 2 +- src/components/BadgeSection.tsx | 6 +-- src/components/CIAnalytics.tsx | 12 ++--- src/components/CodingActivityInsightsCard.tsx | 8 +-- src/components/CodingTimeWidget.tsx | 4 +- src/components/CommitSearchPanel.tsx | 4 +- src/components/CommitTimeChart.tsx | 4 +- src/components/CommunityMetrics.tsx | 10 ++-- src/components/ContributionGraph.tsx | 4 +- src/components/ContributionHeatmap.tsx | 4 +- src/components/CopyLinkButton.tsx | 2 +- src/components/DiscussionsWidget.tsx | 8 +-- src/components/ExportButton.tsx | 4 +- src/components/FriendComparison.tsx | 6 +-- src/components/GitHubAchievements.tsx | 4 +- src/components/GoalTracker.tsx | 16 +++--- src/components/InactiveRepositoriesCard.tsx | 12 ++--- src/components/IssueMetrics.tsx | 8 +-- src/components/LanguageBreakdown.tsx | 6 +-- src/components/LocalCodingTime.tsx | 22 ++++---- src/components/MiniPRTrendChart.tsx | 2 +- src/components/NotificationBell.tsx | 2 +- src/components/PRBreakdownChart.tsx | 10 ++-- src/components/PRMetrics.tsx | 16 +++--- src/components/PRReviewTrendChart.tsx | 2 +- src/components/PersonalRecords.tsx | 8 +-- src/components/PinnedRepos.tsx | 4 +- src/components/PinnedReposWidget.tsx | 10 ++-- src/components/PrivacySettings.tsx | 4 +- src/components/ProjectMetrics.tsx | 22 ++++---- src/components/RecentActivity.tsx | 12 ++--- src/components/RepoHealthPanel.tsx | 2 +- src/components/ShareProfileSection.tsx | 4 +- src/components/ShortcutsModal.tsx | 4 +- src/components/StreakTracker.tsx | 18 +++---- src/components/TodayFocusHero.tsx | 10 ++-- src/components/TopRepos.tsx | 8 +-- src/components/WeeklySummaryCard.tsx | 18 +++---- src/components/WidgetErrorBoundary.tsx | 2 +- .../repo-analytics/RepoAnalyticsExplorer.tsx | 2 +- src/components/repo-analytics/RepoCard.tsx | 2 +- .../repo-analytics/RepoCarousel.tsx | 2 +- tailwind.config.ts | 22 +++++++- 45 files changed, 232 insertions(+), 159 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index b1d91427..e803568a 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -155,6 +155,59 @@ body { animation: fadeUp 0.7s ease-out; } +/* ───────────────────────────────────────── + MICRO-ANIMATION UTILITIES + ───────────────────────────────────────── */ + +/* Staggered animation delays for child elements */ +.stagger-children > *:nth-child(1) { animation-delay: 0ms; } +.stagger-children > *:nth-child(2) { animation-delay: 50ms; } +.stagger-children > *:nth-child(3) { animation-delay: 100ms; } +.stagger-children > *:nth-child(4) { animation-delay: 150ms; } +.stagger-children > *:nth-child(5) { animation-delay: 200ms; } +.stagger-children > *:nth-child(6) { animation-delay: 250ms; } +.stagger-children > *:nth-child(7) { animation-delay: 300ms; } +.stagger-children > *:nth-child(8) { animation-delay: 350ms; } +.stagger-children > *:nth-child(9) { animation-delay: 400ms; } +.stagger-children > *:nth-child(10) { animation-delay: 450ms; } + +/* Shimmer skeleton loading effect */ +.skeleton-shimmer { + background: linear-gradient( + 90deg, + var(--card-muted) 0%, + color-mix(in srgb, var(--card-muted) 60%, var(--border) 40%) 50%, + var(--card-muted) 100% + ); + background-size: 200% 100%; + animation: shimmer 1.8s ease-in-out infinite; +} + +/* Interactive stat cell — hover lift + glow */ +.stat-cell { + transition: transform 200ms ease, box-shadow 200ms ease, background-color 200ms ease, border-color 200ms ease; +} +.stat-cell:hover { + transform: translateY(-2px); + box-shadow: 0 4px 16px -4px rgba(96, 165, 250, 0.15); +} + +/* Interactive list item — subtle left-border reveal + slide */ +.list-item-hover { + transition: transform 200ms ease, background-color 200ms ease, box-shadow 200ms ease, border-color 200ms ease; + border-left: 3px solid transparent; +} +.list-item-hover:hover { + transform: translateX(4px); + border-left-color: var(--accent); + box-shadow: 0 2px 8px -2px rgba(96, 165, 250, 0.1); +} + +/* Progress bar animated fill */ +.progress-fill { + transition: width 600ms cubic-bezier(0.4, 0, 0.2, 1); +} + /* ───────────────────────────────────────── DARK MODE GLOBAL TYPOGRAPHY ───────────────────────────────────────── */ diff --git a/src/components/AIMentorWidget.tsx b/src/components/AIMentorWidget.tsx index 28fe8b3c..9e360a73 100644 --- a/src/components/AIMentorWidget.tsx +++ b/src/components/AIMentorWidget.tsx @@ -39,7 +39,7 @@ function SkeletonCard() { role="status" aria-busy="true" aria-live="polite" - className="rounded-xl border border-[var(--border)] bg-[var(--card)] p-6 shadow-sm animate-pulse" + className="rounded-xl border border-[var(--border)] bg-[var(--card)] p-6 shadow-sm animate-pulse transition-all duration-300 hover:shadow-md hover:-translate-y-1" > Loading AI Mentor insights
@@ -107,7 +107,7 @@ export function AIMentorWidget() { if (error) { return ( -
+

{error}

); @@ -124,7 +124,7 @@ export function AIMentorWidget() { : ""; return ( -
+

diff --git a/src/components/ActivityRingChart.tsx b/src/components/ActivityRingChart.tsx index 2b5a125c..e7135562 100644 --- a/src/components/ActivityRingChart.tsx +++ b/src/components/ActivityRingChart.tsx @@ -81,7 +81,7 @@ export default function ActivityRingChart() { } return ( -
+

Activity Ring diff --git a/src/components/BadgeSection.tsx b/src/components/BadgeSection.tsx index 392054ab..a328d043 100644 --- a/src/components/BadgeSection.tsx +++ b/src/components/BadgeSection.tsx @@ -34,7 +34,7 @@ export default function BadgeSection({ username }: BadgeSectionProps) { const combinedMarkdown = `${streakMarkdown} ${commitsMarkdown}`; return ( -
+

📌 Get Your Badge

@@ -108,13 +108,13 @@ function CopyableCodeBlock({ code }: { code: string }) { }; return ( -
+
{code} diff --git a/src/components/CIAnalytics.tsx b/src/components/CIAnalytics.tsx index 6b908401..80894dcf 100644 --- a/src/components/CIAnalytics.tsx +++ b/src/components/CIAnalytics.tsx @@ -104,7 +104,7 @@ export default function CIAnalytics() { : "Refresh"; return ( -
+

@@ -119,7 +119,7 @@ export default function CIAnalytics() { onClick={fetchCIAnalytics} disabled={isRateLimited || loading} title={isRateLimited ? "GitHub API rate limit reached" : "Refresh CI data"} - className="inline-flex items-center gap-1.5 rounded-md border border-[var(--border)] px-3 py-1.5 text-xs font-medium text-[var(--muted-foreground)] transition-colors hover:bg-[var(--control)] disabled:cursor-not-allowed disabled:opacity-50" + className="inline-flex items-center gap-1.5 rounded-md border border-[var(--border)] px-3 py-1.5 text-xs font-medium text-[var(--muted-foreground)] transition-all hover:bg-[var(--control)] disabled:cursor-not-allowed disabled:opacity-50 hover:opacity-90 active:scale-95" > {loading ? ( @@ -140,7 +140,7 @@ export default function CIAnalytics() { @@ -165,11 +165,11 @@ export default function CIAnalytics() {

) : data ? (
-
+
{stats.map((stat) => (
{stat.value} @@ -181,7 +181,7 @@ export default function CIAnalytics() { ))}
-
+

Flakiest workflow

diff --git a/src/components/CodingActivityInsightsCard.tsx b/src/components/CodingActivityInsightsCard.tsx index d83cfdd6..c13f4ead 100644 --- a/src/components/CodingActivityInsightsCard.tsx +++ b/src/components/CodingActivityInsightsCard.tsx @@ -228,7 +228,7 @@ export default function CodingActivityInsightsCard() { : `${dataWindowLabel} · Commits by hour · Local timezone`; return ( -
+

@@ -251,7 +251,7 @@ export default function CodingActivityInsightsCard() { type="button" onClick={fetchInsights} disabled={loading} - className="flex items-center gap-2 rounded-md border border-[var(--border)] px-3 py-1.5 text-xs font-medium text-[var(--muted-foreground)] transition-colors hover:bg-[var(--control)] disabled:cursor-not-allowed disabled:opacity-50" + className="flex items-center gap-2 rounded-md border border-[var(--border)] px-3 py-1.5 text-xs font-medium text-[var(--muted-foreground)] transition-all hover:bg-[var(--control)] disabled:cursor-not-allowed disabled:opacity-50 hover:opacity-90 active:scale-95" > {loading ? ( <> @@ -272,13 +272,13 @@ export default function CodingActivityInsightsCard() { className="space-y-4" > Loading coding activity insights -
+
{[1, 2, 3].map((item) => ( diff --git a/src/components/CodingTimeWidget.tsx b/src/components/CodingTimeWidget.tsx index 1af55156..d7496804 100644 --- a/src/components/CodingTimeWidget.tsx +++ b/src/components/CodingTimeWidget.tsx @@ -50,7 +50,7 @@ export default function CodingTimeWidget() { if (loading) { return ( -
+
@@ -62,7 +62,7 @@ export default function CodingTimeWidget() { } return ( -
+

Wakatime Coding Activity (7 Days)

diff --git a/src/components/CommitSearchPanel.tsx b/src/components/CommitSearchPanel.tsx index 81fcbd29..d00ec425 100644 --- a/src/components/CommitSearchPanel.tsx +++ b/src/components/CommitSearchPanel.tsx @@ -151,7 +151,7 @@ export default function CommitSearchPanel({ commits, loading }: CommitSearchPane onClick={handleToggle} aria-label={isOpen ? "Collapse commit list" : "Expand commit list"} aria-expanded={isOpen} - className="flex items-center gap-1 rounded-lg border border-[var(--border)] bg-[var(--control)] px-3 py-2 text-sm font-medium text-[var(--foreground)] hover:bg-[var(--control-hover)] transition-colors" + className="flex items-center gap-1 rounded-lg border border-[var(--border)] bg-[var(--control)] px-3 py-2 text-sm font-medium text-[var(--foreground)] hover:bg-[var(--control-hover)] transition-all hover:opacity-90 active:scale-95" > diff --git a/src/components/CommitTimeChart.tsx b/src/components/CommitTimeChart.tsx index 91ab081e..c3a981e3 100644 --- a/src/components/CommitTimeChart.tsx +++ b/src/components/CommitTimeChart.tsx @@ -97,7 +97,7 @@ export default function CommitTimeChart() { }, [fetchContributions]); return ( -
+

Commits by Time of Day @@ -131,7 +131,7 @@ export default function CommitTimeChart() { diff --git a/src/components/CommunityMetrics.tsx b/src/components/CommunityMetrics.tsx index 6ac68834..32f0e02f 100644 --- a/src/components/CommunityMetrics.tsx +++ b/src/components/CommunityMetrics.tsx @@ -62,7 +62,7 @@ export default function CommunityMetrics() { metrics.commentsPosted === 0; return ( -
+

@@ -76,7 +76,7 @@ export default function CommunityMetrics() { type="button" onClick={fetchMetrics} disabled={loading} - className="inline-flex w-full items-center justify-center gap-1.5 rounded-md border border-[var(--border)] px-3 py-1.5 text-xs font-medium text-[var(--muted-foreground)] transition-colors hover:bg-[var(--control)] sm:w-auto disabled:cursor-not-allowed disabled:opacity-60" + className="inline-flex w-full items-center justify-center gap-1.5 rounded-md border border-[var(--border)] px-3 py-1.5 text-xs font-medium text-[var(--muted-foreground)] transition-all hover:bg-[var(--control)] sm:w-auto disabled:cursor-not-allowed disabled:opacity-60 hover:opacity-90 active:scale-95" > {loading ? ( @@ -98,7 +98,7 @@ export default function CommunityMetrics() { @@ -120,7 +120,7 @@ export default function CommunityMetrics() { {stats.map((stat) => (
{stat.value} @@ -133,7 +133,7 @@ export default function CommunityMetrics() {
{isEmpty && ( -
+
No discussion activity yet in this 30-day window.
)} diff --git a/src/components/ContributionGraph.tsx b/src/components/ContributionGraph.tsx index 5d60575e..abce9bb5 100644 --- a/src/components/ContributionGraph.tsx +++ b/src/components/ContributionGraph.tsx @@ -376,7 +376,7 @@ export default function ContributionGraph() { return (
@@ -490,7 +490,7 @@ export default function ContributionGraph() { )} diff --git a/src/components/ContributionHeatmap.tsx b/src/components/ContributionHeatmap.tsx index f177a9ec..b40ce08c 100644 --- a/src/components/ContributionHeatmap.tsx +++ b/src/components/ContributionHeatmap.tsx @@ -329,7 +329,7 @@ export default function ContributionHeatmap({ }; return ( -
+

Contribution Heatmap

@@ -409,7 +409,7 @@ export default function ContributionHeatmap({ )} diff --git a/src/components/CopyLinkButton.tsx b/src/components/CopyLinkButton.tsx index 1774aede..b233894d 100644 --- a/src/components/CopyLinkButton.tsx +++ b/src/components/CopyLinkButton.tsx @@ -30,7 +30,7 @@ export default function CopyLinkButton({ url }: CopyLinkButtonProps) { type="button" onClick={handleCopy} aria-label="Copy profile link" - className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-[var(--control)] border border-[var(--border)] text-[var(--muted-foreground)] hover:text-[var(--foreground)] hover:border-[var(--accent)] rounded-lg text-sm font-medium transition-colors shadow-sm focus:outline-none focus:ring-2 focus:ring-[var(--accent)]/50 active:scale-[0.98]" + className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-[var(--control)] border border-[var(--border)] text-[var(--muted-foreground)] hover:text-[var(--foreground)] hover:border-[var(--accent)] rounded-lg text-sm font-medium transition-all shadow-sm focus:outline-none focus:ring-2 focus:ring-[var(--accent)]/50 active:scale-[0.98] hover:opacity-90 active:scale-95" > {copied ? ( <> diff --git a/src/components/DiscussionsWidget.tsx b/src/components/DiscussionsWidget.tsx index b017632b..daf2905e 100644 --- a/src/components/DiscussionsWidget.tsx +++ b/src/components/DiscussionsWidget.tsx @@ -53,7 +53,7 @@ export default function DiscussionsWidget() { : []; return ( -
+

Discussion Activity

@@ -63,7 +63,7 @@ export default function DiscussionsWidget() { {[1, 2, 3].map((i) => (
))}
@@ -79,11 +79,11 @@ export default function DiscussionsWidget() {
) : ( -
+
{stats.map((stat) => (
diff --git a/src/components/ExportButton.tsx b/src/components/ExportButton.tsx index 631c2212..123743e6 100644 --- a/src/components/ExportButton.tsx +++ b/src/components/ExportButton.tsx @@ -661,7 +661,7 @@ export default function ExportButton() { type="button" onClick={exportCSV} disabled={isExportingCSV} - className="flex w-full items-center justify-center gap-2 rounded-lg border border-[var(--border)] bg-[var(--control)] px-4 py-2 text-sm font-medium text-[var(--card-foreground)] transition-colors hover:border-[var(--accent)] disabled:opacity-50 sm:w-auto sm:min-w-[140px]" + className="flex w-full items-center justify-center gap-2 rounded-lg border border-[var(--border)] bg-[var(--control)] px-4 py-2 text-sm font-medium text-[var(--card-foreground)] transition-all hover:border-[var(--accent)] disabled:opacity-50 sm:w-auto sm:min-w-[140px] hover:opacity-90 active:scale-95" > @@ -673,7 +673,7 @@ export default function ExportButton() { type="button" onClick={exportPDF} disabled={isExportingPDF} - className="flex min-w-0 flex-1 items-center justify-center gap-2 rounded-lg bg-[var(--accent)] px-4 py-2 text-sm font-medium text-[var(--accent-foreground)] transition-colors hover:opacity-90 disabled:opacity-50 sm:min-w-[140px] sm:flex-none" + className="flex min-w-0 flex-1 items-center justify-center gap-2 rounded-lg bg-[var(--accent)] px-4 py-2 text-sm font-medium text-[var(--accent-foreground)] transition-all hover:opacity-90 disabled:opacity-50 sm:min-w-[140px] sm:flex-none active:scale-95" suppressHydrationWarning > diff --git a/src/components/FriendComparison.tsx b/src/components/FriendComparison.tsx index a484ac9d..59d120a1 100644 --- a/src/components/FriendComparison.tsx +++ b/src/components/FriendComparison.tsx @@ -193,7 +193,7 @@ export default function FriendComparison() { }; return ( -
+

@@ -288,7 +288,7 @@ export default function FriendComparison() { @@ -384,7 +384,7 @@ export default function FriendComparison() { diff --git a/src/components/GitHubAchievements.tsx b/src/components/GitHubAchievements.tsx index 749f4580..4cd4b6a2 100644 --- a/src/components/GitHubAchievements.tsx +++ b/src/components/GitHubAchievements.tsx @@ -12,7 +12,7 @@ export default function GitHubAchievements({ error = null, }: GitHubAchievementsProps) { return ( -
+

GitHub Achievements

@@ -29,7 +29,7 @@ export default function GitHubAchievements({ diff --git a/src/components/GoalTracker.tsx b/src/components/GoalTracker.tsx index 83920c35..ef761fde 100644 --- a/src/components/GoalTracker.tsx +++ b/src/components/GoalTracker.tsx @@ -241,17 +241,17 @@ export default function GoalTracker() { if (loading) { return ( -
+
Loading weekly goals