From 7931f30c515bfeaabe36a8d2833db8b552385936 Mon Sep 17 00:00:00 2001 From: MuhammadMkm17 Date: Wed, 19 Nov 2025 18:11:59 +0530 Subject: [PATCH 1/3] Fix: 1080x1440px downloaded image irrespective of device --- components/ExportCard.tsx | 41 ++++++++++ components/GitWrapCard.tsx | 5 +- components/layouts/ClassicLayout.tsx | 10 ++- index.css | 25 ++++++ pages/UserPage.tsx | 112 ++++++++++++++++++++------- 5 files changed, 161 insertions(+), 32 deletions(-) create mode 100644 components/ExportCard.tsx diff --git a/components/ExportCard.tsx b/components/ExportCard.tsx new file mode 100644 index 0000000..22e8ac7 --- /dev/null +++ b/components/ExportCard.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import GitWrapCard from './GitWrapCard'; +import type { UserStats, Theme } from '../types'; +import { CardLayout } from '../types'; + +interface ExportCardProps { + userData: UserStats; + funMessage: string; + theme: Theme; + layout: CardLayout; +} + +/** + * Export-only wrapper that renders a GitWrap card at a fixed 1080x1440 size. + * The component is meant to be mounted in a hidden container before converting to PNG. + */ +const ExportCard: React.FC = ({ userData, funMessage, theme, layout }) => { + return ( +
+ +
+ ); +}; + +export default ExportCard; diff --git a/components/GitWrapCard.tsx b/components/GitWrapCard.tsx index 7b9cb30..8ab9af4 100644 --- a/components/GitWrapCard.tsx +++ b/components/GitWrapCard.tsx @@ -11,10 +11,11 @@ interface GitWrapCardProps { funMessage: string; theme: Theme; layout: CardLayout; + isExport?: boolean; } -const GitWrapCard = forwardRef(({ userData, funMessage, theme, layout }, ref) => { - const props = { userData, funMessage, theme }; +const GitWrapCard = forwardRef(({ userData, funMessage, theme, layout, isExport = false }, ref) => { + const props = { userData, funMessage, theme, isExport }; switch (layout) { case CardLayout.Modern: diff --git a/components/layouts/ClassicLayout.tsx b/components/layouts/ClassicLayout.tsx index e21f12b..b489d8a 100644 --- a/components/layouts/ClassicLayout.tsx +++ b/components/layouts/ClassicLayout.tsx @@ -10,6 +10,7 @@ interface LayoutProps { userData: UserStats; funMessage: string; theme: Theme; + isExport?: boolean; } // Helper to convert flat calendar to heatmap weeks structure @@ -77,7 +78,7 @@ function buildHeatmapWeeks(calendar: Array<{ date: string; count: number }>) { return weeks; } -const ClassicLayout = forwardRef(({ userData, funMessage, theme }, ref) => { +const ClassicLayout = forwardRef(({ userData, funMessage, theme, isExport = false }, ref) => { const { classes } = theme; const isRetro = theme.name === 'Retro'; const isSpace = theme.name === 'Space'; @@ -148,7 +149,12 @@ const ClassicLayout = forwardRef(({ userData, funMe }; return ( -
+
{/* Retro LCD scan lines overlay */} {isRetro && ( <> diff --git a/index.css b/index.css index 2bd8d85..1be1bb7 100644 --- a/index.css +++ b/index.css @@ -20,3 +20,28 @@ html, body, #root { h1, h2, h3, h4, h5, h6, strong { font-weight: 600; } + +/* Export-mode breakpoint overrides so sm: utilities behave consistently */ +.export-mode .sm\:block { + display: block !important; +} + +.export-mode .sm\:flex { + display: flex !important; +} + +.export-mode .sm\:inline-flex { + display: inline-flex !important; +} + +.export-mode .sm\:grid { + display: grid !important; +} + +.export-mode .sm\:inline-block { + display: inline-block !important; +} + +.export-mode .sm\:hidden { + display: none !important; +} diff --git a/pages/UserPage.tsx b/pages/UserPage.tsx index f800e60..ae3d07d 100644 --- a/pages/UserPage.tsx +++ b/pages/UserPage.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { createRoot } from 'react-dom/client'; import { useParams, Link } from 'react-router-dom'; import type { UserStats, Theme } from '../types'; import { AspectRatio, CardLayout } from '../types'; @@ -6,6 +7,7 @@ import { fetchGitHubStats } from '../services/github'; import { generateFunMessage } from '../services/geminiService'; import { THEMES } from '../constants'; import GitWrapCard from '../components/GitWrapCard'; +import ExportCard from '../components/ExportCard'; import ThemeSelector from '../components/ThemeSelector'; import SocialShare from '../components/SocialShare'; import { DownloadIcon } from '../components/icons/DownloadIcon'; @@ -67,32 +69,88 @@ const UserPage: React.FC = () => { }, [fetchData]); const handleDownload = async (aspectRatio: AspectRatio) => { - if (!cardRef.current) return; + if (!userData) return; setIsDownloading(aspectRatio); - const cardElement = cardRef.current; - const originalStyle = cardElement.getAttribute('style'); - - // Define target dimensions based on aspect ratio - // For 3:4 (Social Post), use standardized mobile-friendly dimensions - let targetWidth: number; - let targetHeight: number; - - if (aspectRatio === AspectRatio.Social) { // "3:4" - // Use 1080x1440 which is a standard mobile post size (maintains 3:4 ratio) - targetWidth = 1080; - targetHeight = 1440; - } + const targetWidth = 1080; + const targetHeight = 1440; + + const exportContainer = document.createElement('div'); + exportContainer.style.position = 'fixed'; + exportContainer.style.left = '0'; + exportContainer.style.top = '0'; + exportContainer.style.width = `${targetWidth}px`; + exportContainer.style.height = `${targetHeight}px`; + exportContainer.style.opacity = '0'; + exportContainer.style.pointerEvents = 'none'; + exportContainer.style.overflow = 'hidden'; + exportContainer.style.zIndex = '-1'; + exportContainer.setAttribute('aria-hidden', 'true'); + + document.body.appendChild(exportContainer); + + const root = createRoot(exportContainer); + root.render( + + ); try { - // Temporarily set explicit dimensions on the card element to ensure proper rendering - cardElement.style.width = `${targetWidth}px`; - cardElement.style.height = `${targetHeight}px`; + // Allow React to flush and layout to settle + await new Promise((resolve) => requestAnimationFrame(() => resolve(undefined))); + await new Promise((resolve) => setTimeout(resolve, 200)); + + // Ensure fonts (if available) are loaded before capture + const fontSet = (document as Document & { fonts?: FontFaceSet }).fonts; + if (fontSet?.ready) { + await fontSet.ready.catch(() => undefined); + } - const dataUrl = await htmlToImage.toPng(cardElement, { - pixelRatio: 2, // Higher quality on download + // Wait for images to load + const images: HTMLImageElement[] = Array.from(exportContainer.querySelectorAll('img')); + if (images.length > 0) { + await Promise.race([ + Promise.all( + images.map( + (img) => + new Promise((resolve) => { + if (img.complete) { + resolve(); + } else { + img.addEventListener('load', () => resolve(), { once: true }); + img.addEventListener('error', () => resolve(), { once: true }); + } + }) + ) + ), + new Promise((resolve) => setTimeout(resolve, 3000)), + ]); + } + + const cardElement = exportContainer.firstElementChild as HTMLElement | null; + const captureTarget = cardElement ?? exportContainer; + const computedStyles = getComputedStyle(captureTarget); + const backgroundColor = computedStyles.backgroundColor; + + const dataUrl = await htmlToImage.toPng(captureTarget, { + pixelRatio: 1, // Force exact 1080x1440 output cacheBust: true, - allowTaint: false, + width: targetWidth, + height: targetHeight, + style: { + opacity: '1', + visibility: 'visible', + transform: 'none', + filter: 'none', + }, + backgroundColor: + backgroundColor && backgroundColor !== 'rgba(0, 0, 0, 0)' + ? backgroundColor + : undefined, }); const link = document.createElement('a'); @@ -101,17 +159,15 @@ const UserPage: React.FC = () => { link.download = `gitwrap-2025-${safeUsername}-classic-${safeAspectRatio}.png`; link.href = dataUrl; link.click(); - } catch(err) { + } catch (err) { console.error('Failed to download image:', err); const message = err instanceof Error ? err.message : String(err); - alert(`Sorry, there was an error creating the image. This can be caused by external resources (like avatars) failing to load. Please try again.\n\nError: ${message}`); + alert( + `Sorry, there was an error creating the image. This can be caused by external resources (like avatars) failing to load. Please try again.\n\nError: ${message}` + ); } finally { - // Restore original styles - if (originalStyle) { - cardElement.setAttribute('style', originalStyle); - } else { - cardElement.removeAttribute('style'); - } + root.unmount(); + document.body.removeChild(exportContainer); setIsDownloading(null); } }; From bd36b2a79f3fd0fbee9260a4d7c04c0876635d50 Mon Sep 17 00:00:00 2001 From: MuhammadMkm17 Date: Sun, 23 Nov 2025 14:37:07 +0530 Subject: [PATCH 2/3] Fix: homepage responsiveness and remove unwanted animation during download --- components/Contributors.tsx | 5 +- components/layouts/ClassicLayout.tsx | 58 +++++++-------- components/layouts/CompactLayout.tsx | 20 +++--- index.css | 10 +++ pages/HomePage.tsx | 102 ++++++++++++++++----------- pages/UserPage.tsx | 16 +++-- 6 files changed, 122 insertions(+), 89 deletions(-) diff --git a/components/Contributors.tsx b/components/Contributors.tsx index de3e0cc..e3f38d0 100644 --- a/components/Contributors.tsx +++ b/components/Contributors.tsx @@ -112,13 +112,12 @@ const Contributors: React.FC = () => { }, []); if (!contributors) { - return ( -
Loading contributors…
+ return (
Loading contributors…
); } return ( -
+

Contributors

{contributors.map((c) => ( diff --git a/components/layouts/ClassicLayout.tsx b/components/layouts/ClassicLayout.tsx index b489d8a..6a163bc 100644 --- a/components/layouts/ClassicLayout.tsx +++ b/components/layouts/ClassicLayout.tsx @@ -112,7 +112,7 @@ const ClassicLayout = forwardRef(({ userData, funMe style={{ gridTemplateColumns: `repeat(${totalWeeks}, minmax(0, 1fr))` }} > {normalizedWeeks.map((week, idx) => ( -
+
{week.monthLabel}
))} @@ -121,7 +121,7 @@ const ClassicLayout = forwardRef(({ userData, funMe {/* Heatmap with weekday labels */}
{/* Weekday labels */} -
+
{weekdayLabels.map((label, idx) => (
{label} @@ -151,7 +151,7 @@ const ClassicLayout = forwardRef(({ userData, funMe return (
@@ -263,7 +263,7 @@ const ClassicLayout = forwardRef(({ userData, funMe crossOrigin="anonymous" className="w-16 h-16 rounded-2xl shadow-xl ring-2 ring-white/10" /> -
+
{userData.githubAnniversary}Y
@@ -291,7 +291,7 @@ const ClassicLayout = forwardRef(({ userData, funMe crossOrigin="anonymous" className="w-14 h-14 sm:w-20 sm:h-20 rounded-xl sm:rounded-2xl shadow-xl ring-2 sm:ring-4 ring-white/10" /> -
+
{userData.githubAnniversary}Y
@@ -302,7 +302,7 @@ const ClassicLayout = forwardRef(({ userData, funMe
2025
-
Year in Review
+
Year in Review
@@ -352,7 +352,7 @@ const ClassicLayout = forwardRef(({ userData, funMe }`}>
{userData.bestHour}:00
-
Best Hour
+
Best Hour
(({ userData, funMe }`}>
👀
{userData.totalPRReviews}
-
Reviews
+
Reviews
(({ userData, funMe }`}>
{userData.totalStarsGiven}
-
Stars Given
+
Stars Given
(({ userData, funMe }`}>
🔥
{userData.longestStreakDays}
-
Day Streak
+
Day Streak
@@ -442,9 +442,9 @@ const ClassicLayout = forwardRef(({ userData, funMe }`}>
-
Best Hour
+
Best Hour
{userData.bestHour}:00
-
peak coding time
+
peak coding time
@@ -458,9 +458,9 @@ const ClassicLayout = forwardRef(({ userData, funMe }`}>
-
Reviews
+
Reviews
{userData.totalPRReviews}
-
code reviews
+
code reviews
👀
@@ -473,9 +473,9 @@ const ClassicLayout = forwardRef(({ userData, funMe }`}>
-
Stars
+
Stars
{userData.totalStarsGiven}
-
given
+
given
@@ -523,7 +523,7 @@ const ClassicLayout = forwardRef(({ userData, funMe
{lang.name}
-
+
#{idx + 1}
@@ -580,9 +580,9 @@ const ClassicLayout = forwardRef(({ userData, funMe
🔥
-
Streak
+
Streak
{userData.longestStreakDays}
-
days
+
days
(({ userData, funMe
📅
-
Peak Day
+
Peak Day
{userData.bestDayOfWeek.slice(0, 3)}
-
most active
+
most active
(({ userData, funMe
📦
-
Repos
+
Repos
{userData.totalRepositories}
-
projects
+
projects
(({ userData, funMe
{userData.yearOverYearGrowth?.overallGrowth ?? 0 >= 0 ? '📈' : '📉'}
-
Growth
+
Growth
= 0 ? 'text-green-400' : 'text-red-400'}`}> {userData.yearOverYearGrowth?.overallGrowth ?? 0 >= 0 ? '+' : ''}{userData.yearOverYearGrowth?.overallGrowth ?? 0}%
-
vs 2024
+
vs 2024
@@ -646,7 +646,7 @@ const ClassicLayout = forwardRef(({ userData, funMe {/* Mobile: Centered layout */}
🏆
-
Top Repository
+
Top Repository
{userData.topRepos[0].name}
{userData.topRepos[0].contributions} commits @@ -658,7 +658,7 @@ const ClassicLayout = forwardRef(({ userData, funMe {/* Desktop: Original horizontal layout */}
-
⭐ Top Repository
+
⭐ Top Repository
{userData.topRepos[0].name}
{userData.topRepos[0].contributions} commits @@ -681,7 +681,7 @@ const ClassicLayout = forwardRef(({ userData, funMe }`}>

2025 Activity

-
+
Less
{[0, 1, 2, 3, 4].map((level) => ( @@ -734,7 +734,7 @@ const ClassicLayout = forwardRef(({ userData, funMe {/* Footer */} -
+
Powered by GitWrap • gitwrap.app
diff --git a/components/layouts/CompactLayout.tsx b/components/layouts/CompactLayout.tsx index e1f03a6..3a30699 100644 --- a/components/layouts/CompactLayout.tsx +++ b/components/layouts/CompactLayout.tsx @@ -125,7 +125,7 @@ const CompactLayout = forwardRef(({ userData, funMe
2025
-
GitWrap
+
GitWrap
@@ -136,22 +136,22 @@ const CompactLayout = forwardRef(({ userData, funMe
{userData.totalCommits > 999 ? `${(userData.totalCommits / 1000).toFixed(1)}k` : userData.totalCommits}
-
Commits
+
Commits
{userData.totalPRs}
-
PRs
+
PRs
{userData.totalIssues}
-
Issues
+
Issues
{userData.totalPRReviews}
-
Reviews
+
Reviews
@@ -159,7 +159,7 @@ const CompactLayout = forwardRef(({ userData, funMe

2025 Activity

-
+
Less
{[0, 1, 2, 3, 4].map((level) => ( @@ -209,17 +209,17 @@ const CompactLayout = forwardRef(({ userData, funMe
{userData.longestStreakDays}
-
Day Streak 🔥
+
Day Streak 🔥
{userData.bestDayOfWeek.slice(0, 3)}
-
Peak Day
+
Peak Day
{userData.githubAnniversary}Y
-
On GitHub
+
On GitHub
@@ -236,7 +236,7 @@ const CompactLayout = forwardRef(({ userData, funMe
{/* Footer */} -
+
GitWrap • Year in Review
diff --git a/index.css b/index.css index 1be1bb7..dcbdd8c 100644 --- a/index.css +++ b/index.css @@ -45,3 +45,13 @@ h1, h2, h3, h4, h5, h6, strong { .export-mode .sm\:hidden { display: none !important; } + +/* Utility to hide scrollbars without disabling scroll */ +body.no-scrollbar { + scrollbar-width: none; +} + +body.no-scrollbar::-webkit-scrollbar { + width: 0; + height: 0; +} diff --git a/pages/HomePage.tsx b/pages/HomePage.tsx index 22d8b40..fce9890 100644 --- a/pages/HomePage.tsx +++ b/pages/HomePage.tsx @@ -1,4 +1,3 @@ - import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { SparklesIcon } from '../components/icons/SparklesIcon'; @@ -19,49 +18,70 @@ const HomePage: React.FC = () => { }; return ( -
-
-
- -
-

- GitWrap 2025 -

-

- Get your personalized GitHub year in review. See your coding journey, celebrate your achievements, and share your story. -

-
+
+
- {/* Mobile: Remove aspect ratio constraint, Desktop: Keep 3:4 ratio */} -
-
- -
+ {/* Let the card grow naturally so no content is clipped */} +
+
From 78116fd66c169ce9ceb93f97b1e1125cae6612e1 Mon Sep 17 00:00:00 2001 From: MuhammadMkm17 Date: Sun, 23 Nov 2025 19:37:45 +0530 Subject: [PATCH 3/3] Fix: font size issue and UI changes integration --- components/layouts/ClassicLayout.tsx | 2 +- pages/HomePage.tsx | 114 ++++++++++++--------------- 2 files changed, 53 insertions(+), 63 deletions(-) diff --git a/components/layouts/ClassicLayout.tsx b/components/layouts/ClassicLayout.tsx index 6a163bc..a14d08c 100644 --- a/components/layouts/ClassicLayout.tsx +++ b/components/layouts/ClassicLayout.tsx @@ -735,7 +735,7 @@ const ClassicLayout = forwardRef(({ userData, funMe {/* Footer */}
- Powered by GitWrap • gitwrap.app + Powered by GitWrap • 2025
diff --git a/pages/HomePage.tsx b/pages/HomePage.tsx index fce9890..74c9d7a 100644 --- a/pages/HomePage.tsx +++ b/pages/HomePage.tsx @@ -1,3 +1,4 @@ + import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { SparklesIcon } from '../components/icons/SparklesIcon'; @@ -18,72 +19,61 @@ const HomePage: React.FC = () => { }; return ( -
-