@@ -62,7 +62,7 @@ export async function GET(
6262 // Fetch Profile
6363 const { data : profile , error } = await supabase
6464 . from ( "user_profiles" )
65- . select ( "* " )
65+ . select ( "persona_id, persona_name, persona_tagline, persona_confidence, total_repos, total_commits, axes_json, narrative_json " )
6666 . eq ( "user_id" , userId )
6767 . single ( ) ;
6868
@@ -85,11 +85,83 @@ export async function GET(
8585 const aura = getPersonaAura ( profile . persona_id ) ;
8686
8787 const baseUrl = process . env . NEXT_PUBLIC_APP_URL || "http://localhost:8108" ;
88+ const displayUrl = new URL ( baseUrl ) . host ;
89+
8890 const bgUrl = new URL ( format === "story" ? aura . verticalBackground : aura . background , baseUrl ) . toString ( ) ;
8991 const iconUrl = new URL ( aura . icon , baseUrl ) . toString ( ) ;
9092
91- const width = format === "story" ? 1080 : format === "square" ? 1080 : 1200 ;
92- const height = format === "story" ? 1920 : format === "square" ? 1080 : 630 ;
93+ // Dimensions (2x for Retina quality)
94+ const scale = 2 ;
95+ const width = ( format === "story" ? 1080 : format === "square" ? 1080 : 1200 ) * scale ;
96+ const height = ( format === "story" ? 1920 : format === "square" ? 1080 : 630 ) * scale ;
97+
98+ // -------------------------------------------------------------------------
99+ // Metric Computation
100+ // -------------------------------------------------------------------------
101+ const axes = profile . axes_json as any ;
102+ const narrative = profile . narrative_json as any ;
103+
104+ let metrics = {
105+ strongest : "N/A" ,
106+ style : "Balanced" ,
107+ rhythm : "Mixed" ,
108+ peak : "Varied"
109+ } ;
110+
111+ if ( axes ) {
112+ // 1. Strongest
113+ let maxScore = - 1 ;
114+ let maxName = "" ;
115+ const AXIS_NAMES : Record < string , string > = {
116+ automation_heaviness : "Automation" ,
117+ guardrail_strength : "Guardrails" ,
118+ iteration_loop_intensity : "Loops" ,
119+ planning_signal : "Planning" ,
120+ surface_area_per_change : "Scope" ,
121+ shipping_rhythm : "Rhythm" ,
122+ } ;
123+ for ( const [ key , val ] of Object . entries ( axes ) ) {
124+ const axis = val as { score : number } ;
125+ if ( axis . score > maxScore && AXIS_NAMES [ key ] ) {
126+ maxScore = axis . score ;
127+ maxName = AXIS_NAMES [ key ] ;
128+ }
129+ }
130+ const strongest = maxName ? `${ maxName } ${ maxScore } ` : "N/A" ;
131+
132+ // 2. Style
133+ let style = "Mixed" ; // Default
134+ const A = ( axes . automation_heaviness ?. score || 0 ) ;
135+ const B = ( axes . guardrail_strength ?. score || 0 ) ;
136+ const C = ( axes . iteration_loop_intensity ?. score || 0 ) ;
137+ const D = ( axes . planning_signal ?. score || 0 ) ;
138+ const E = ( axes . surface_area_per_change ?. score || 0 ) ;
139+ const F = ( axes . shipping_rhythm ?. score || 0 ) ;
140+
141+ if ( A >= 70 && C >= 65 ) style = "Fast Builder" ;
142+ else if ( B >= 70 && A >= 50 ) style = "Safe Shipper" ;
143+ else if ( F >= 70 && C >= 60 ) style = "Rapid Cycler" ;
144+ else if ( B >= 65 && C < 40 ) style = "Steady Hand" ;
145+ else if ( A >= 60 && B < 40 ) style = "Bold Mover" ;
146+ else if ( D >= 70 ) style = "Deep Planner" ;
147+ else if ( E >= 70 ) style = "Wide Scoper" ;
148+ else style = "Balanced" ;
149+
150+ // 3. Rhythm
151+ let rhythm = "Mixed" ;
152+ const rScore = axes . shipping_rhythm ?. score || 0 ;
153+ if ( rScore >= 65 ) rhythm = "Bursty" ;
154+ else if ( rScore < 35 ) rhythm = "Steady" ;
155+
156+ metrics = { ...metrics , strongest, style, rhythm } ;
157+ }
158+
159+ // Insight Text
160+ let insightText = "Your aggregated profile balances these styles across your repositories." ;
161+ if ( narrative ) {
162+ if ( narrative . insight ) insightText = narrative . insight ;
163+ else if ( narrative . summary ) insightText = narrative . summary ;
164+ }
93165
94166 // Resolve fonts
95167 const [ fontDataNormal , fontDataBold ] = await Promise . all ( [ fontNormalPromise , fontBoldPromise ] ) ;
@@ -98,6 +170,9 @@ export async function GET(
98170 if ( fontDataNormal ) fonts . push ( { name : "Space Grotesk" , data : fontDataNormal , style : "normal" , weight : 400 } ) ;
99171 if ( fontDataBold ) fonts . push ( { name : "Space Grotesk" , data : fontDataBold , style : "normal" , weight : 700 } ) ;
100172
173+ const paddingX = 60 * scale ;
174+ const paddingY = 60 * scale ;
175+
101176 return new ImageResponse (
102177 (
103178 < div
@@ -112,6 +187,7 @@ export async function GET(
112187 backgroundSize : "cover" ,
113188 backgroundPosition : "center" ,
114189 fontFamily : '"Space Grotesk", sans-serif' ,
190+ fontSize : 24 * scale ,
115191 } }
116192 >
117193 { /* Overlay */ }
@@ -132,48 +208,55 @@ export async function GET(
132208 justifyContent : "space-between" ,
133209 width : "100%" ,
134210 height : "100%" ,
135- padding : format === "story" ? "120px 80px" : "80px" ,
211+ padding : ` ${ paddingY } px ${ paddingX } px` ,
136212 } }
137213 >
138- { /* Header */ }
139- < div style = { { display : "flex" , flexDirection : "column" } } >
214+ { /* Header Area */ }
215+ < div style = { { display : "flex" , flexDirection : "column" , width : "100%" } } >
140216 < div
141217 style = { {
142- fontSize : format === "story" ? 32 : 24 ,
218+ display : "flex" ,
219+ fontSize : 18 * scale ,
143220 fontWeight : 700 ,
144221 letterSpacing : "0.2em" ,
145222 textTransform : "uppercase" ,
146- color : "rgba(255,255,255,0.6 )" ,
147- marginBottom : 20 ,
223+ color : "rgba(255,255,255,0.7 )" ,
224+ marginBottom : 12 * scale ,
148225 } }
149226 >
150227 My Unified VCP
151228 </ div >
152229 < div
153230 style = { {
154- fontSize : format === "story" ? 80 : 64 ,
231+ display : "flex" ,
232+ fontSize : 64 * scale ,
155233 fontWeight : 700 ,
156234 color : "white" ,
157- lineHeight : 1.1 ,
158- marginBottom : 20 ,
235+ marginBottom : 8 * scale ,
236+ lineHeight : 1 ,
237+ textShadow : "0 2px 10px rgba(0,0,0,0.2)" ,
238+ maxWidth : "90%" ,
159239 } }
160240 >
161241 { personaName }
162242 </ div >
163243 < div
164244 style = { {
165- fontSize : format === "story" ? 40 : 32 ,
245+ display : "flex" ,
246+ fontSize : 28 * scale ,
166247 color : "rgba(255,255,255,0.9)" ,
167- lineHeight : 1.4 ,
168- marginBottom : 30 ,
248+ marginBottom : 12 * scale ,
249+ fontWeight : 400 ,
250+ maxWidth : "90%" ,
169251 } }
170252 >
171253 { personaTagline }
172254 </ div >
173255 { personaConfidence && (
174256 < div
175257 style = { {
176- fontSize : format === "story" ? 32 : 24 ,
258+ display : "flex" ,
259+ fontSize : 20 * scale ,
177260 fontWeight : 500 ,
178261 color : "rgba(255,255,255,0.7)" ,
179262 } }
@@ -183,80 +266,124 @@ export async function GET(
183266 ) }
184267 </ div >
185268
186- { /* Icon */ }
269+ { /* Icon (Top Right) */ }
187270 < img
188271 src = { iconUrl }
189- width = { 160 }
190- height = { 160 }
272+ width = { 140 * scale }
273+ height = { 140 * scale }
191274 style = { {
192275 position : "absolute" ,
193- top : format === "story" ? 120 : 80 ,
194- right : format === "story" ? 80 : 80 ,
195- borderRadius : 80 ,
196- border : "4px solid rgba(255,255,255,0.3)" ,
276+ top : paddingY ,
277+ right : paddingX ,
278+ borderRadius : 70 * scale ,
279+ border : `${ 4 * scale } px solid rgba(255,255,255,0.2)` ,
280+ boxShadow : `0 ${ 8 * scale } px ${ 30 * scale } px rgba(0,0,0,0.3)` ,
197281 objectFit : "cover" ,
198282 } }
199283 />
200284
201- { /* Metrics */ }
285+ { /* Insight Box */ }
202286 < div
203287 style = { {
204288 display : "flex" ,
205- flexDirection : "row" , // Explicit flex-direction
206- gap : 40 ,
207- marginTop : "auto" ,
289+ flexDirection : "row" ,
290+ width : "100%" ,
291+ marginTop : "auto" , // Push to bottom for Story, or natural flow
292+ marginBottom : 20 * scale ,
293+ padding : 24 * scale ,
294+ backgroundColor : "rgba(255,255,255,0.1)" ,
295+ border : `${ 1 * scale } px solid rgba(255,255,255,0.2)` ,
296+ borderRadius : 24 * scale ,
297+ alignItems : "center" ,
208298 } }
209299 >
210- < div style = { { display : "flex" , flexDirection : "column" } } >
211- < div
212- style = { {
213- fontSize : 20 ,
214- fontWeight : 600 ,
215- letterSpacing : "0.1em" ,
216- textTransform : "uppercase" ,
217- color : "rgba(255,255,255,0.6)" ,
218- } }
219- >
220- Repositories
221- </ div >
222- < div style = { { fontSize : 56 , fontWeight : 700 , color : "white" } } >
223- { totalRepos }
224- </ div >
300+ < div
301+ style = { {
302+ display : "flex" ,
303+ fontSize : 22 * scale ,
304+ color : "rgba(255,255,255,0.9)" ,
305+ lineHeight : 1.3 ,
306+ fontWeight : 400 ,
307+ maxHeight : 100 * scale ,
308+ overflow : "hidden" ,
309+ textOverflow : "ellipsis" ,
310+ } }
311+ >
312+ { insightText }
225313 </ div >
226- < div style = { { display : "flex" , flexDirection : "column" } } >
314+ </ div >
315+
316+ { /* Grid Area */ }
317+ < div
318+ style = { {
319+ display : "flex" ,
320+ flexDirection : "row" ,
321+ flexWrap : "wrap" ,
322+ gap : 20 * scale ,
323+ width : "100%" ,
324+ marginBottom : 20 * scale ,
325+ } }
326+ >
327+ { [
328+ { label : "Strongest" , value : metrics . strongest } ,
329+ { label : "Style" , value : metrics . style } ,
330+ { label : "Rhythm" , value : metrics . rhythm } ,
331+ { label : "Peak" , value : metrics . peak } ,
332+ ] . map ( ( item , i ) => (
227333 < div
334+ key = { i }
228335 style = { {
229- fontSize : 20 ,
230- fontWeight : 600 ,
231- letterSpacing : "0.1em" ,
232- textTransform : "uppercase" ,
233- color : "rgba(255,255,255,0.6)" ,
336+ display : "flex" ,
337+ flexDirection : "column" ,
338+ width : "48%" , // Responsive 2-column grid
339+ flexGrow : 1 ,
340+ height : 120 * scale ,
341+ padding : 20 * scale ,
342+ backgroundColor : "rgba(255,255,255,0.1)" ,
343+ border : `${ 1 * scale } px solid rgba(255,255,255,0.2)` ,
344+ borderRadius : 20 * scale ,
345+ justifyContent : "center" ,
234346 } }
235347 >
236- Commits
348+ < div
349+ style = { {
350+ display : "flex" ,
351+ fontSize : 14 * scale ,
352+ fontWeight : 700 ,
353+ letterSpacing : "0.15em" ,
354+ textTransform : "uppercase" ,
355+ color : "rgba(255,255,255,0.6)" ,
356+ marginBottom : 6 * scale ,
357+ } }
358+ >
359+ { item . label }
360+ </ div >
361+ < div style = { { display : "flex" , fontSize : 32 * scale , fontWeight : 700 , color : "white" } } >
362+ { item . value }
363+ </ div >
237364 </ div >
238- < div style = { { fontSize : 56 , fontWeight : 700 , color : "white" } } >
239- { totalCommits . toLocaleString ( ) }
240- </ div >
241- </ div >
365+ ) ) }
242366 </ div >
243367
244368 { /* Footer */ }
245369 < div
246370 style = { {
247371 display : "flex" ,
248- flexDirection : "row" , // Explicit flex-direction
372+ flexDirection : "row" ,
249373 justifyContent : "space-between" ,
250- borderTop : "2px solid rgba(255,255,255,0.2)" ,
251- paddingTop : 40 ,
252- marginTop : 60 ,
374+ width : "100%" ,
375+ maxWidth : "100%" ,
376+ borderTop : `${ 1 * scale } px solid rgba(255,255,255,0.15)` ,
377+ paddingTop : 16 * scale ,
378+ marginTop : 0 ,
379+ marginBottom : 0 ,
253380 } }
254381 >
255- < div style = { { fontSize : 24 , fontWeight : 500 , color : "rgba(255,255,255,0.5 )" } } >
256- vibed.dev
382+ < div style = { { display : "flex" , fontSize : 20 * scale , fontWeight : 500 , color : "rgba(255,255,255,0.6 )" } } >
383+ { displayUrl }
257384 </ div >
258- < div style = { { fontSize : 24 , color : "rgba(255,255,255,0.5 )" } } >
259- #VCP
385+ < div style = { { display : "flex" , fontSize : 20 * scale , color : "rgba(255,255,255,0.6 )" } } >
386+ { totalRepos } repos • { totalCommits . toLocaleString ( ) } commits
260387 </ div >
261388 </ div >
262389 </ div >
0 commit comments