11/**
2- * Master Card SVG generation - Ultimate Profile Dashboard (830px ).
2+ * Master Card SVG generation - Ultimate Profile Dashboard (1000px ).
33 * Designed to cover the full width of a GitHub README with a high-end profile look.
44 */
55
@@ -14,158 +14,190 @@ function escapeXml(s) {
1414
1515function generateMasterCardSVG ( options ) {
1616 const {
17- username, name, totalPRs = 0 , openPRs = 0 , mergedPRs = 0 , repoCount = 0 ,
17+ username, name, avatarUrl, bio, location, company, followers = 0 , following = 0 ,
18+ totalPRs = 0 , openPRs = 0 , mergedPRs = 0 , repoCount = 0 ,
1819 languages = [ ] , contributions = 0 , totalCommits = 0 , repoList = [ ] ,
1920 contributionDays = [ ] , currentStreak = 0 , longestStreak = 0 ,
2021 totalIssues = 0 , openIssues = 0 , closedIssues = 0 , totalStars = 0 ,
21- linesChanged = 0 , weekMap = { } , colors, hideBorder, cardWidth = 830
22+ linesChanged = 0 , weekMap = { } , colors, hideBorder, cardWidth = 1000
2223 } = options ;
2324
24- const pad = 32 ;
25+ const pad = 40 ;
2526 const innerW = cardWidth - pad * 2 ;
26- const hba = hideBorder ? `rx="16 "` : `rx="16 " stroke="#30363d" stroke-width="2"` ;
27+ const hba = hideBorder ? `rx="20 "` : `rx="20 " stroke="#30363d" stroke-width="2"` ;
2728
2829 const accentColor = ( colors && colors . accent_color ) || "58a6ff" ;
2930 const titleColor = ( colors && colors . title_color ) || "58a6ff" ;
3031 const textColor = ( colors && colors . text_color ) || "c9d1d9" ;
32+ const bgColor = ( colors && colors . bg_color ) || "0d1117" ;
3133
3234 let y = 0 ;
3335 let content = "" ;
3436
35- // --- HEADER SECTION ---
37+ // --- HEADER SECTION WITH AVATAR ---
3638 content += `
37- <g transform="translate(${ pad } , 45)">
38- <text x="0" y="24" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="34" font-weight="900" fill="#${ titleColor } ">${ escapeXml ( name || username ) } </text>
39- <text x="0" y="50" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="14" font-weight="600" fill="#8b949e">GitHub Profile @${ escapeXml ( username . toLowerCase ( ) ) } </text>
40- <rect x="0" y="62" width="100" height="6" fill="#${ accentColor } " rx="3"/>
39+ <g transform="translate(${ pad } , 50)">
40+ ${ avatarUrl ? `
41+ <defs>
42+ <clipPath id="avatarClip">
43+ <circle cx="50" cy="50" r="50"/>
44+ </clipPath>
45+ </defs>
46+ <image href="${ avatarUrl } " x="0" y="0" width="100" height="100" clip-path="url(#avatarClip)"/>
47+ ` : `<circle cx="50" cy="50" r="50" fill="#30363d"/>` }
48+
49+ <g transform="translate(${ avatarUrl ? 130 : 0 } , 15)">
50+ <text x="0" y="24" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="42" font-weight="900" fill="#${ titleColor } ">${ escapeXml ( name || username ) } </text>
51+ <text x="0" y="55" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="18" font-weight="600" fill="#8b949e">@${ escapeXml ( username . toLowerCase ( ) ) } • ${ repoCount } Repositories</text>
52+ ${ bio ? `<text x="0" y="80" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="14" fill="#${ textColor } " opacity="0.8">${ escapeXml ( bio . length > 80 ? bio . substring ( 0 , 77 ) + '...' : bio ) } </text>` : "" }
53+ </g>
54+
55+ <g transform="translate(${ innerW - 200 } , 25)">
56+ <text x="100" y="20" text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="24" font-weight="800" fill="#${ textColor } ">${ followers . toLocaleString ( ) } </text>
57+ <text x="100" y="40" text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="12" font-weight="600" fill="#8b949e" style="text-transform:uppercase">Followers</text>
58+
59+ <text x="180" y="20" text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="24" font-weight="800" fill="#${ textColor } ">${ following . toLocaleString ( ) } </text>
60+ <text x="180" y="40" text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="12" font-weight="600" fill="#8b949e" style="text-transform:uppercase">Following</text>
61+ </g>
4162 </g>` ;
42- y = 135 ;
63+ y = 180 ;
64+
65+ content += `<line x1="${ pad } " y1="${ y } " x2="${ cardWidth - pad } " y2="${ y } " stroke="#30363d" stroke-width="1" opacity="0.3"/>` ;
66+ y += 60 ;
4367
4468 // --- MAIN STATS ROW ---
4569 const statW = Math . floor ( innerW / 5 ) ;
4670 const heroStats = [
47- { label : "Commits" , value : totalCommits . toLocaleString ( ) , color : accentColor } ,
48- { label : "Stars" , value : totalStars . toLocaleString ( ) , color : "eab308" } ,
49- { label : "PRs " , value : totalPRs , color : "8b5cf6" } ,
50- { label : "Issues" , value : totalIssues , color : "f85149" } ,
71+ { label : "Total Commits" , value : totalCommits . toLocaleString ( ) , color : accentColor } ,
72+ { label : "Total Stars" , value : totalStars . toLocaleString ( ) , color : "eab308" } ,
73+ { label : "Pull Requests " , value : totalPRs , color : "8b5cf6" } ,
74+ { label : "Issues Fixed " , value : totalIssues , color : "f85149" } ,
5175 { label : "Contributions" , value : contributions . toLocaleString ( ) , color : "39d353" } ,
5276 ] ;
5377
5478 heroStats . forEach ( ( s , i ) => {
5579 content += `
5680 <g transform="translate(${ pad + i * statW } , ${ y } )">
57- <text x="${ statW / 2 } " y="20 " text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="26 " font-weight="800 " fill="#${ s . color } ">${ s . value } </text>
58- <text x="${ statW / 2 } " y="42 " text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="11 " font-weight="600" fill="#8b949e" style="text-transform:uppercase;letter-spacing:1px ">${ s . label } </text>
81+ <text x="${ statW / 2 } " y="25 " text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="32 " font-weight="900 " fill="#${ s . color } ">${ s . value } </text>
82+ <text x="${ statW / 2 } " y="52 " text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="13 " font-weight="600" fill="#8b949e" style="text-transform:uppercase;letter-spacing:1.5px ">${ s . label } </text>
5983 </g>` ;
6084 } ) ;
61- y += 90 ;
85+ y += 100 ;
6286
63- content += `<line x1="${ pad } " y1="${ y } " x2="${ cardWidth - pad } " y2="${ y } " stroke="#30363d" stroke-width="1.5 " opacity="0.3"/>` ;
64- y += 45 ;
87+ content += `<line x1="${ pad } " y1="${ y } " x2="${ cardWidth - pad } " y2="${ y } " stroke="#30363d" stroke-width="1" opacity="0.3"/>` ;
88+ y += 60 ;
6589
66- // --- MIDDLE GRID: Languages (Left) & Weekly Activity (Right) ---
67- const gridW = ( innerW - 40 ) / 2 ;
90+ // --- MIDDLE GRID ---
91+ const gridW = ( innerW - 60 ) / 2 ;
6892
69- // Top Languages with bars
93+ // Top Languages
7094 content += `<g transform="translate(${ pad } , ${ y } )">
71- <text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="18 " font-weight="800" fill="#${ titleColor } ">Language Usage </text>
72- <g transform="translate(0, 25 )">` ;
95+ <text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="22 " font-weight="800" fill="#${ titleColor } ">Language Dominance </text>
96+ <g transform="translate(0, 35 )">` ;
7397 const { getLanguageColor } = require ( "./languages" ) ;
74- const topLangs = languages . slice ( 0 , 7 ) ;
98+ const topLangs = languages . slice ( 0 , 8 ) ;
7599 topLangs . forEach ( ( lang , i ) => {
76- const ly = i * 28 ;
77- const barMax = gridW - 130 ;
100+ const ly = i * 35 ;
101+ const barMax = gridW - 150 ;
78102 const barW = ( lang . percentage / 100 ) * barMax ;
79103 content += `
80104 <g transform="translate(0, ${ ly } )">
81- <circle cx="6 " cy="10 " r="5 " fill="#${ getLanguageColor ( lang . name ) } "/>
82- <text x="20 " y="14 " font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="13 " fill="#${ textColor } ">${ escapeXml ( lang . name ) } </text>
83- <rect x="110 " y="7 " width="${ barMax } " height="8 " rx="4 " fill="#30363d" opacity="0.4"/>
84- <rect x="110 " y="7 " width="${ barW } " height="8 " rx="4 " fill="#${ getLanguageColor ( lang . name ) } "/>
85- <text x="${ gridW } " y="14 " text-anchor="end" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="11 " font-weight="600 " fill="#8b949e">${ lang . percentage . toFixed ( 1 ) } %</text>
105+ <circle cx="8 " cy="12 " r="6 " fill="#${ getLanguageColor ( lang . name ) } "/>
106+ <text x="25 " y="17 " font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="15" font-weight="600 " fill="#${ textColor } ">${ escapeXml ( lang . name ) } </text>
107+ <rect x="120 " y="8 " width="${ barMax } " height="10 " rx="5 " fill="#30363d" opacity="0.4"/>
108+ <rect x="120 " y="8 " width="${ barW } " height="10 " rx="5 " fill="#${ getLanguageColor ( lang . name ) } "/>
109+ <text x="${ gridW } " y="17 " text-anchor="end" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="13 " font-weight="700 " fill="#8b949e">${ lang . percentage . toFixed ( 1 ) } %</text>
86110 </g>` ;
87111 } ) ;
88112 content += `</g></g>` ;
89113
90114 // Weekly Activity Chart
91- const daysOfWeek = [ "Sun " , "Mon " , "Tue " , "Wed " , "Thu " , "Fri " , "Sat " ] ;
115+ const daysOfWeek = [ "Sunday " , "Monday " , "Tuesday " , "Wednesday " , "Thursday " , "Friday " , "Saturday " ] ;
92116 const maxWeekly = Math . max ( ...Object . values ( weekMap ) , 1 ) ;
93- content += `<g transform="translate(${ pad + gridW + 40 } , ${ y } )">
94- <text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="18 " font-weight="800" fill="#${ titleColor } ">Weekly Activity </text>
95- <g transform="translate(0, 25 )">` ;
117+ content += `<g transform="translate(${ pad + gridW + 60 } , ${ y } )">
118+ <text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="22 " font-weight="800" fill="#${ titleColor } ">Weekly Productivity </text>
119+ <g transform="translate(0, 35 )">` ;
96120 daysOfWeek . forEach ( ( day , i ) => {
97- const ly = i * 28 ;
121+ const ly = i * 35 ;
98122 const count = weekMap [ i ] || 0 ;
99- const barW = ( count / maxWeekly ) * ( gridW - 100 ) ;
123+ const barW = ( count / maxWeekly ) * ( gridW - 120 ) ;
100124 content += `
101125 <g transform="translate(0, ${ ly } )">
102- <text x="0" y="14 " font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="13 " fill="#8b949e">${ day } </text>
103- <rect x="50 " y="7 " width="${ gridW - 100 } " height="8 " rx="4 " fill="#30363d" opacity="0.4"/>
104- <rect x="50 " y="7 " width="${ barW } " height="8 " rx="4 " fill="#39d353" opacity="0.8"/>
105- <text x="${ gridW } " y="14 " text-anchor="end" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="12 " font-weight="700 " fill="#39d353">${ count . toLocaleString ( ) } </text>
126+ <text x="0" y="17 " font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="14" font-weight="600 " fill="#8b949e">${ day } </text>
127+ <rect x="90 " y="8 " width="${ gridW - 120 } " height="10 " rx="5 " fill="#30363d" opacity="0.4"/>
128+ <rect x="90 " y="8 " width="${ barW } " height="10 " rx="5 " fill="#39d353" opacity="0.8"/>
129+ <text x="${ gridW } " y="17 " text-anchor="end" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="14 " font-weight="800 " fill="#39d353">${ count . toLocaleString ( ) } </text>
106130 </g>` ;
107131 } ) ;
108132 content += `</g></g>` ;
109133
110- y += 240 ;
111- content += `<line x1="${ pad } " y1="${ y } " x2="${ cardWidth - pad } " y2="${ y } " stroke="#30363d" stroke-width="1.5 " opacity="0.3"/>` ;
112- y += 45 ;
134+ y += 330 ;
135+ content += `<line x1="${ pad } " y1="${ y } " x2="${ cardWidth - pad } " y2="${ y } " stroke="#30363d" stroke-width="1" opacity="0.3"/>` ;
136+ y += 60 ;
113137
114- // --- LOWER GRID: PR Projects & Streaks ---
138+ // --- LOWER GRID: Repos & Streaks ---
115139 content += `<g transform="translate(${ pad } , ${ y } )">
116140 <g>
117- <text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="18 " font-weight="800" fill="#${ titleColor } ">Top Active Repositories </text>
118- <g transform="translate(0, 20 )">` ;
119- repoList . slice ( 0 , 4 ) . forEach ( ( repo , i ) => {
120- content += `<text x="0" y="${ i * 24 + 14 } " font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="13 " fill="#c9d1d9">• ${ escapeXml ( repo . name . length > 35 ? repo . name . substring ( 0 , 32 ) + '...' : repo . name ) } <tspan fill="#${ accentColor } " font-weight="800 ">(${ repo . count } PRs)</tspan></text>` ;
141+ <text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="22 " font-weight="800" fill="#${ titleColor } ">Top Contributions by Repository </text>
142+ <g transform="translate(0, 30 )">` ;
143+ repoList . slice ( 0 , 6 ) . forEach ( ( repo , i ) => {
144+ content += `<text x="0" y="${ i * 30 + 15 } " font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="15 " fill="#c9d1d9">• ${ escapeXml ( repo . name . length > 50 ? repo . name . substring ( 0 , 47 ) + '...' : repo . name ) } <tspan fill="#${ accentColor } " font-weight="900 ">(${ repo . count } PRs)</tspan></text>` ;
121145 } ) ;
122146 content += `</g></g>
123- <g transform="translate(${ gridW + 40 } , 0)">
124- <text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="18 " font-weight="800" fill="#${ titleColor } ">Performance & Streaks </text>
125- <g transform="translate(0, 20 )">
126- <rect width="${ gridW } " height="50 " rx="12 " fill="#39d353" opacity="0.1"/>
127- <text x="16 " y="30 " font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="15 " font-weight="700 " fill="#39d353">🔥 Current Streak: ${ currentStreak } Days</text>
128- <g transform="translate(0, 62 )">
129- <rect width="${ gridW } " height="50 " rx="12 " fill="#f97316" opacity="0.1"/>
130- <text x="16 " y="30 " font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="15 " font-weight="700 " fill="#f97316">🏆 All-time Best: ${ longestStreak } Days</text>
147+ <g transform="translate(${ gridW + 60 } , 0)">
148+ <text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="22 " font-weight="800" fill="#${ titleColor } ">Consistency & Momentum </text>
149+ <g transform="translate(0, 30 )">
150+ <rect width="${ gridW } " height="70 " rx="15 " fill="#39d353" opacity="0.1"/>
151+ <text x="25 " y="42 " font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="20 " font-weight="800 " fill="#39d353">🔥 Current Streak: ${ currentStreak } Days</text>
152+ <g transform="translate(0, 85 )">
153+ <rect width="${ gridW } " height="70 " rx="15 " fill="#f97316" opacity="0.1"/>
154+ <text x="25 " y="42 " font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="20 " font-weight="800 " fill="#f97316">🏆 All-time Best: ${ longestStreak } Days</text>
131155 </g>
132156 </g>
133157 </g>
134158 </g>` ;
135- y += 135 ;
159+ y += 240 ;
136160
137- content += `<line x1="${ pad } " y1="${ y } " x2="${ cardWidth - pad } " y2="${ y } " stroke="#30363d" stroke-width="1.5 " opacity="0.3"/>` ;
138- y += 45 ;
161+ content += `<line x1="${ pad } " y1="${ y } " x2="${ cardWidth - pad } " y2="${ y } " stroke="#30363d" stroke-width="1" opacity="0.3"/>` ;
162+ y += 60 ;
139163
140164 // --- FOOTER: CONTRIBUTION HEATMAP ---
141165 content += `<g transform="translate(${ pad } , ${ y } )">
142- <text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="18 " font-weight="800" fill="#${ titleColor } ">Full Contribution History (Last 12 Months) </text>
143- <g transform="translate(0, 20 )">` ;
166+ <text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="22 " font-weight="800" fill="#${ titleColor } ">Annual Contribution Heatmap </text>
167+ <g transform="translate(0, 35 )">` ;
144168 const sortedDays = [ ...contributionDays ] . sort ( ( a , b ) => String ( a . date ) . localeCompare ( String ( b . date ) ) ) ;
145- const recent = sortedDays . slice ( - 371 ) ;
146- const cell = 11.5 , gap = 3 , step = cell + gap ;
169+
170+ // On 1000px width, we can fit more weeks.
171+ // Standard GitHub heatmap is ~53 weeks.
172+ // cell 14, gap 4 = 18 per week. 53 * 18 = 954.
173+ const cell = 14 , gap = 4 , step = cell + gap ;
174+ const maxWeeks = Math . floor ( innerW / step ) ;
175+ const recent = sortedDays . slice ( - ( maxWeeks * 7 ) ) ;
176+
147177 recent . forEach ( ( d , i ) => {
148178 const col = Math . floor ( i / 7 ) ;
149179 const row = i % 7 ;
150- content += `<rect x="${ col * step } " y="${ row * step } " width="${ cell } " height="${ cell } " rx="3" fill="#${ getContributionColorByLevel ( d . level ) } "/>` ;
180+ content += `<rect x="${ col * step } " y="${ row * step } " width="${ cell } " height="${ cell } " rx="3" fill="#${ getContributionColorByLevel ( d . level ) } ">
181+ <title>${ d . date } : ${ d . count } contributions</title>
182+ </rect>` ;
151183 } ) ;
152184 content += `</g></g>` ;
153- y += 150 ;
185+ y += 180 ;
154186
155- const cardHeight = y + 45 ;
187+ const cardHeight = y + 80 ;
156188
157189 return `<svg xmlns="http://www.w3.org/2000/svg" width="${ cardWidth } " height="${ cardHeight } " viewBox="0 0 ${ cardWidth } ${ cardHeight } ">
158190 <defs>
159191 <linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
160- <stop offset="0%" style="stop-color:#${ accentColor } ;stop-opacity:0.08 "/>
161- <stop offset="100%" style="stop-color:#0d1117 ;stop-opacity:0"/>
192+ <stop offset="0%" style="stop-color:#${ accentColor } ;stop-opacity:0.12 "/>
193+ <stop offset="100%" style="stop-color:#${ bgColor } ;stop-opacity:0"/>
162194 </linearGradient>
163195 </defs>
164- <rect width="${ cardWidth } " height="${ cardHeight } " fill="#0d1117 " ${ hba } />
196+ <rect width="${ cardWidth } " height="${ cardHeight } " fill="#${ bgColor } " ${ hba } />
165197 <rect width="${ cardWidth } " height="${ cardHeight } " fill="url(#bgGrad)" ${ hba } />
166- <rect width="${ cardWidth } " height="8 " fill="#${ accentColor } " rx="16 "/>
198+ <rect width="${ cardWidth } " height="10 " fill="#${ accentColor } " rx="20 "/>
167199 ${ content }
168- <text x="${ cardWidth - pad } " y="${ cardHeight - 20 } " text-anchor="end" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="11" fill="#8b949e" opacity="0.4">gitlyy full dashboard • updated every 30m </text>
200+ <text x="${ cardWidth - pad } " y="${ cardHeight - 30 } " text-anchor="end" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="13" font-weight="600" fill="#8b949e" opacity="0.5">GITLYY ULTIMATE DASHBOARD • UPDATED EVERY 30M </text>
169201</svg>` ;
170202}
171203
0 commit comments