@@ -76,17 +76,48 @@ export async function generateShareCard(data: ShareCardData): Promise<Blob> {
7676 throw new Error ( "Could not get 2d context from canvas" ) ;
7777 }
7878
79- // 1. Draw Base Dark Gradient Background
79+ // 0. Pre-load all assets concurrently (Logo, Avatar, and Badges)
80+ let logoImg : HTMLImageElement | null = null ;
81+ let avatarImg : HTMLImageElement | null = null ;
82+
83+ const logoPromise = loadImage ( "/img/logo.png" )
84+ . then ( ( img ) => {
85+ logoImg = img ;
86+ } )
87+ . catch ( ( err ) => console . warn ( "Could not load logo: " , err ) ) ;
88+
89+ const avatarPromise = loadImage ( data . avatarUrl )
90+ . then ( ( img ) => {
91+ avatarImg = img ;
92+ } )
93+ . catch ( ( err ) => console . warn ( "Could not load avatar: " , err ) ) ;
94+
95+ const badgePromises = data . allBadges . map ( async ( badge ) => {
96+ try {
97+ const img = await loadImage ( badge . image ) ;
98+ return { image : badge . image , img, loaded : true } ;
99+ } catch {
100+ return { image : badge . image , img : null , loaded : false } ;
101+ }
102+ } ) ;
103+
104+ const [ , , loadedBadges ] = await Promise . all ( [
105+ logoPromise ,
106+ avatarPromise ,
107+ Promise . all ( badgePromises ) ,
108+ ] ) ;
109+
110+ // 1. Draw Base Light Radial Gradient Background
80111 const bgGrad = ctx . createRadialGradient ( 600 , 315 , 100 , 600 , 315 , 700 ) ;
81- bgGrad . addColorStop ( 0 , "#1e1b4b " ) ; // Dark Indigo center
82- bgGrad . addColorStop ( 1 , "#09090e " ) ; // Near-black edges
112+ bgGrad . addColorStop ( 0 , "#ffffff " ) ; // Pure white center
113+ bgGrad . addColorStop ( 1 , "#f1f5f9 " ) ; // Soft slate-grey-blue edges
83114 ctx . fillStyle = bgGrad ;
84115 ctx . fillRect ( 0 , 0 , 1200 , 630 ) ;
85116
86- // 2. Draw Decorative Ambient Glow Circles
117+ // 2. Draw Decorative Subtle Ambient Glow Circles
87118 // Left ambient glow (indigo)
88119 const leftGlow = ctx . createRadialGradient ( 250 , 250 , 50 , 250 , 250 , 350 ) ;
89- leftGlow . addColorStop ( 0 , "rgba(99, 102, 241, 0.12 )" ) ;
120+ leftGlow . addColorStop ( 0 , "rgba(99, 102, 241, 0.05 )" ) ;
90121 leftGlow . addColorStop ( 1 , "rgba(99, 102, 241, 0)" ) ;
91122 ctx . fillStyle = leftGlow ;
92123 ctx . beginPath ( ) ;
@@ -95,80 +126,86 @@ export async function generateShareCard(data: ShareCardData): Promise<Blob> {
95126
96127 // Right ambient glow (violet/purple)
97128 const rightGlow = ctx . createRadialGradient ( 950 , 380 , 50 , 950 , 380 , 350 ) ;
98- rightGlow . addColorStop ( 0 , "rgba(139, 92, 246 , 0.12 )" ) ;
99- rightGlow . addColorStop ( 1 , "rgba(139, 92, 246 , 0)" ) ;
129+ rightGlow . addColorStop ( 0 , "rgba(168, 85, 247 , 0.05 )" ) ;
130+ rightGlow . addColorStop ( 1 , "rgba(168, 85, 247 , 0)" ) ;
100131 ctx . fillStyle = rightGlow ;
101132 ctx . beginPath ( ) ;
102133 ctx . arc ( 950 , 380 , 350 , 0 , Math . PI * 2 ) ;
103134 ctx . fill ( ) ;
104135
105- // 3. Draw Glassmorphic Card Container
136+ // 3. Draw Premium Light Glassmorphic Card Container
106137 ctx . save ( ) ;
138+ // Premium soft drop shadow
139+ ctx . shadowColor = "rgba(15, 23, 42, 0.06)" ;
140+ ctx . shadowBlur = 30 ;
141+ ctx . shadowOffsetY = 12 ;
142+ ctx . shadowOffsetX = 0 ;
143+
107144 drawRoundedRect ( ctx , 50 , 50 , 1100 , 530 , 24 ) ;
108145 // Fill gradient
109146 const cardGrad = ctx . createLinearGradient ( 50 , 50 , 1150 , 580 ) ;
110- cardGrad . addColorStop ( 0 , "rgba(30, 41, 59 , 0.45 )" ) ;
111- cardGrad . addColorStop ( 1 , "rgba(15, 23, 42 , 0.65 )" ) ;
147+ cardGrad . addColorStop ( 0 , "rgba(255, 255, 255 , 0.92 )" ) ;
148+ cardGrad . addColorStop ( 1 , "rgba(248, 250, 252 , 0.95 )" ) ;
112149 ctx . fillStyle = cardGrad ;
113150 ctx . fill ( ) ;
151+
152+ // Disable shadow for border/stroke
153+ ctx . shadowBlur = 0 ;
154+ ctx . shadowOffsetY = 0 ;
155+
114156 // Stroke border
115157 ctx . lineWidth = 1.5 ;
116158 const strokeGrad = ctx . createLinearGradient ( 50 , 50 , 1150 , 580 ) ;
117- strokeGrad . addColorStop ( 0 , "rgba(255, 255, 255, 0.12 )" ) ;
118- strokeGrad . addColorStop ( 0.5 , "rgba(255, 255, 255 , 0.04 )" ) ;
119- strokeGrad . addColorStop ( 1 , "rgba(99, 102, 241, 0.25 )" ) ;
159+ strokeGrad . addColorStop ( 0 , "rgba(255, 255, 255, 0.9 )" ) ;
160+ strokeGrad . addColorStop ( 0.5 , "rgba(226, 232, 240 , 0.8 )" ) ;
161+ strokeGrad . addColorStop ( 1 , "rgba(99, 102, 241, 0.15 )" ) ;
120162 ctx . strokeStyle = strokeGrad ;
121163 ctx . stroke ( ) ;
122164 ctx . restore ( ) ;
123165
124166 // 4. Draw Recode Hive Logo (Top-Left)
125- // Glowing hexagon logo mark
126- ctx . save ( ) ;
127- drawHexagon ( ctx , 110 , 115 , 22 ) ;
128- const logoGrad = ctx . createLinearGradient ( 90 , 95 , 130 , 135 ) ;
129- logoGrad . addColorStop ( 0 , "#6366f1" ) ; // Indigo
130- logoGrad . addColorStop ( 1 , "#a855f7" ) ; // Purple
131- ctx . fillStyle = logoGrad ;
132- ctx . fill ( ) ;
133- ctx . restore ( ) ;
167+ if ( logoImg ) {
168+ ctx . drawImage ( logoImg , 86 , 91 , 48 , 48 ) ;
169+ } else {
170+ // Fallback: Glowing hexagon logo mark
171+ ctx . save ( ) ;
172+ drawHexagon ( ctx , 110 , 115 , 22 ) ;
173+ const logoGrad = ctx . createLinearGradient ( 90 , 95 , 130 , 135 ) ;
174+ logoGrad . addColorStop ( 0 , "#6366f1" ) ; // Indigo
175+ logoGrad . addColorStop ( 1 , "#a855f7" ) ; // Purple
176+ ctx . fillStyle = logoGrad ;
177+ ctx . fill ( ) ;
178+ ctx . restore ( ) ;
179+ }
134180
135181 // Logo text
136182 ctx . font = "bold 38px Inter, system-ui, -apple-system, sans-serif" ;
137- ctx . fillStyle = "#ffffff" ;
183+ ctx . fillStyle = "#0f172a" ; // Deep slate
138184 ctx . fillText ( "recode" , 155 , 120 ) ;
139185
140- ctx . fillStyle = "#f59e0b " ; // Golden yellow
186+ ctx . fillStyle = "#d97706 " ; // Amber-gold (highly readable on light theme)
141187 ctx . fillText ( "hive" , 280 , 120 ) ;
142188
143189 // Subtitle / Tagline
144190 ctx . font = "500 16px Inter, system-ui, -apple-system, sans-serif" ;
145- ctx . fillStyle = "#64748b " ; // Cool slate gray
191+ ctx . fillStyle = "#475569 " ; // Slate gray (darker for readability)
146192 ctx . fillText ( "Learn, Build & Grow with Open Source" , 155 , 146 ) ;
147193
148- // 5. Load and Draw Contributor Avatar
194+ // 5. Draw Contributor Avatar
149195 const avatarX = 160 ;
150196 const avatarY = 285 ;
151197 const avatarRadius = 70 ;
152198
153- let avatarLoaded = false ;
154- let avatarImg : HTMLImageElement | null = null ;
155- try {
156- avatarImg = await loadImage ( data . avatarUrl ) ;
157- avatarLoaded = true ;
158- } catch ( err ) {
159- console . warn ( "Could not load contributor avatar: " , err ) ;
160- }
161-
162199 // Draw Avatar ring glow
163200 ctx . save ( ) ;
164201 ctx . beginPath ( ) ;
165202 ctx . arc ( avatarX , avatarY , avatarRadius + 5 , 0 , Math . PI * 2 ) ;
166- ctx . strokeStyle = "rgba(99, 102, 241, 0.5 )" ;
203+ ctx . strokeStyle = "rgba(99, 102, 241, 0.15 )" ;
167204 ctx . lineWidth = 4 ;
168205 ctx . stroke ( ) ;
169206 ctx . restore ( ) ;
170207
171- if ( avatarLoaded && avatarImg ) {
208+ if ( avatarImg ) {
172209 ctx . save ( ) ;
173210 ctx . beginPath ( ) ;
174211 ctx . arc ( avatarX , avatarY , avatarRadius , 0 , Math . PI * 2 ) ;
@@ -213,33 +250,33 @@ export async function generateShareCard(data: ShareCardData): Promise<Blob> {
213250
214251 // Username
215252 ctx . font = "bold 38px Inter, system-ui, -apple-system, sans-serif" ;
216- ctx . fillStyle = "#ffffff" ;
253+ ctx . fillStyle = "#0f172a" ; // Deep slate
217254 ctx . fillText ( `@${ data . username } ` , 260 , 275 ) ;
218255
219256 // Rank Pill
220257 const pillX = 260 ;
221258 const pillY = 295 ;
222- const pillW = 145 ;
259+ const pillW = 165 ;
223260 const pillH = 34 ;
224261 const pillR = 17 ;
225262
226263 ctx . save ( ) ;
227264 drawRoundedRect ( ctx , pillX , pillY , pillW , pillH , pillR ) ;
228- ctx . fillStyle = "rgba(99, 102, 241, 0.15 )" ;
265+ ctx . fillStyle = "rgba(99, 102, 241, 0.08 )" ;
229266 ctx . fill ( ) ;
230- ctx . strokeStyle = "rgba(99, 102, 241, 0.4 )" ;
267+ ctx . strokeStyle = "rgba(99, 102, 241, 0.25 )" ;
231268 ctx . lineWidth = 1.2 ;
232269 ctx . stroke ( ) ;
233270
234271 ctx . font = "bold 15px Inter, system-ui, -apple-system, sans-serif" ;
235- ctx . fillStyle = "#93c5fd " ; // Sky blue text
272+ ctx . fillStyle = "#4f46e5 " ; // Indigo/violet rank text
236273 ctx . textAlign = "center" ;
237274 ctx . fillText ( `Leaderboard Rank #${ data . rank } ` , pillX + pillW / 2 , pillY + 22 ) ;
238275 ctx . restore ( ) ;
239276
240277 // 7. Draw Divider & Stats Section
241278 // Divider
242- ctx . strokeStyle = "rgba(255, 255, 255 , 0.08 )" ;
279+ ctx . strokeStyle = "rgba(226, 232, 240 , 0.8 )" ;
243280 ctx . lineWidth = 1.5 ;
244281 ctx . beginPath ( ) ;
245282 ctx . moveTo ( 760 , 220 ) ;
@@ -249,24 +286,24 @@ export async function generateShareCard(data: ShareCardData): Promise<Blob> {
249286 // Merged PRs Column
250287 ctx . textAlign = "center" ;
251288 ctx . font = "bold 68px Inter, system-ui, -apple-system, sans-serif" ;
252- ctx . fillStyle = "#ffffff" ;
289+ ctx . fillStyle = "#0f172a" ; // Deep slate
253290 ctx . fillText ( data . prs . toString ( ) , 640 , 280 ) ;
254291
255292 ctx . font = "bold 14px Inter, system-ui, -apple-system, sans-serif" ;
256- ctx . fillStyle = "#64748b" ;
293+ ctx . fillStyle = "#64748b" ; // Slate grey label
257294 ctx . fillText ( "MERGED PRS" , 640 , 310 ) ;
258295
259296 // Total Points Column
260297 ctx . font = "bold 68px Inter, system-ui, -apple-system, sans-serif" ;
261- ctx . fillStyle = "#8b5cf6 " ; // Highlight purple
298+ ctx . fillStyle = "#7c3aed " ; // Violet highlight
262299 ctx . fillText ( data . points . toString ( ) , 880 , 280 ) ;
263300
264301 ctx . font = "bold 14px Inter, system-ui, -apple-system, sans-serif" ;
265- ctx . fillStyle = "#64748b" ;
302+ ctx . fillStyle = "#64748b" ; // Slate grey label
266303 ctx . fillText ( "TOTAL POINTS" , 880 , 310 ) ;
267304
268305 // 8. Draw Lower Divider & Badges Section
269- ctx . strokeStyle = "rgba(255, 255, 255 , 0.08 )" ;
306+ ctx . strokeStyle = "rgba(226, 232, 240 , 0.8 )" ;
270307 ctx . beginPath ( ) ;
271308 ctx . moveTo ( 100 , 395 ) ;
272309 ctx . lineTo ( 1100 , 395 ) ;
@@ -278,19 +315,6 @@ export async function generateShareCard(data: ShareCardData): Promise<Blob> {
278315 ctx . fillStyle = "#64748b" ;
279316 ctx . fillText ( "UNLOCKED CONTRIBUTOR ACHIEVEMENTS" , 100 , 430 ) ;
280317
281- // Load and Draw all Badges
282- // Pre-load all images to draw them inline
283- const badgePromises = data . allBadges . map ( async ( badge ) => {
284- try {
285- const img = await loadImage ( badge . image ) ;
286- return { image : badge . image , img, loaded : true } ;
287- } catch {
288- return { image : badge . image , img : null , loaded : false } ;
289- }
290- } ) ;
291-
292- const loadedBadges = await Promise . all ( badgePromises ) ;
293-
294318 // Position and draw badges
295319 const badgeWidth = 64 ;
296320 const badgeHeight = 64 ;
@@ -305,8 +329,8 @@ export async function generateShareCard(data: ShareCardData): Promise<Blob> {
305329 ctx . save ( ) ;
306330 if ( isEarned ) {
307331 ctx . globalAlpha = 1.0 ;
308- // Add subtle drop shadow under earned badges
309- ctx . shadowColor = "rgba(139, 92, 246 , 0.4 )" ;
332+ // Soft drop shadow under earned badges for light theme
333+ ctx . shadowColor = "rgba(99, 102, 241 , 0.2 )" ;
310334 ctx . shadowBlur = 12 ;
311335 ctx . shadowOffsetY = 4 ;
312336 } else {
@@ -326,7 +350,7 @@ export async function generateShareCard(data: ShareCardData): Promise<Blob> {
326350 0 ,
327351 Math . PI * 2
328352 ) ;
329- ctx . fillStyle = isEarned ? "#8b5cf6 " : "#334155 " ;
353+ ctx . fillStyle = isEarned ? "#7c3aed " : "#cbd5e1 " ;
330354 ctx . fill ( ) ;
331355 }
332356 ctx . restore ( ) ;
0 commit comments