@@ -4,8 +4,9 @@ export const runtime = "edge";
44
55const OG_WIDTH = 1200 ;
66const OG_HEIGHT = 630 ;
7- const DEFAULT_TITLE = "Claws.supply" ;
8- const DEFAULT_DESCRIPTION = "OpenClaw AI agent template marketplace." ;
7+ const TITLE_MAX_LENGTH = 110 ;
8+ const DESCRIPTION_MAX_LENGTH = 180 ;
9+ const LOGO_PATH = new URL ( "./logo.png" , import . meta. url ) ;
910
1011function truncateText ( value : string , maxLength : number ) {
1112 const normalized = value . trim ( ) ;
@@ -17,13 +18,96 @@ function truncateText(value: string, maxLength: number) {
1718 return `${ normalized . slice ( 0 , Math . max ( maxLength - 1 , 1 ) ) . trimEnd ( ) } …` ;
1819}
1920
20- export function GET ( request : Request ) {
21+ function normalizeText (
22+ value : string | null ,
23+ maxLength : number ,
24+ ) : string | undefined {
25+ if ( ! value ) {
26+ return undefined ;
27+ }
28+
29+ const normalized = value . trim ( ) ;
30+ if ( normalized . length === 0 ) {
31+ return undefined ;
32+ }
33+
34+ return truncateText ( normalized , maxLength ) ;
35+ }
36+
37+ function arrayBufferToBase64 ( buffer : ArrayBuffer ) : string {
38+ const bytes = new Uint8Array ( buffer ) ;
39+ const chunkSize = 0x8000 ;
40+ let binary = "" ;
41+
42+ for ( let index = 0 ; index < bytes . length ; index += chunkSize ) {
43+ binary += String . fromCharCode ( ...bytes . subarray ( index , index + chunkSize ) ) ;
44+ }
45+
46+ return btoa ( binary ) ;
47+ }
48+
49+ async function loadLogoDataUri ( ) : Promise < string | null > {
50+ try {
51+ const response = await fetch ( LOGO_PATH ) ;
52+ if ( ! response . ok ) {
53+ return null ;
54+ }
55+
56+ const contentType = response . headers . get ( "content-type" ) ?? "image/png" ;
57+ const arrayBuffer = await response . arrayBuffer ( ) ;
58+ const base64 = arrayBufferToBase64 ( arrayBuffer ) ;
59+ return `data:${ contentType } ;base64,${ base64 } ` ;
60+ } catch {
61+ return null ;
62+ }
63+ }
64+
65+ const logoDataUriPromise = loadLogoDataUri ( ) ;
66+
67+ function getTitleFontSize ( titleLength : number , hasDescription : boolean ) {
68+ if ( ! hasDescription ) {
69+ if ( titleLength <= 32 ) return 88 ;
70+ if ( titleLength <= 56 ) return 76 ;
71+ if ( titleLength <= 84 ) return 66 ;
72+ return 58 ;
73+ }
74+
75+ if ( titleLength <= 28 ) return 74 ;
76+ if ( titleLength <= 54 ) return 64 ;
77+ if ( titleLength <= 80 ) return 56 ;
78+ return 50 ;
79+ }
80+
81+ function getDescriptionFontSize (
82+ descriptionLength : number ,
83+ hasTitle : boolean ,
84+ ) {
85+ if ( ! hasTitle ) {
86+ if ( descriptionLength <= 80 ) return 46 ;
87+ if ( descriptionLength <= 130 ) return 40 ;
88+ return 34 ;
89+ }
90+
91+ if ( descriptionLength <= 84 ) return 34 ;
92+ if ( descriptionLength <= 136 ) return 30 ;
93+ return 27 ;
94+ }
95+
96+ export async function GET ( request : Request ) {
2197 const { searchParams } = new URL ( request . url ) ;
22- const title = truncateText ( searchParams . get ( "title" ) ?? DEFAULT_TITLE , 110 ) ;
23- const description = truncateText (
24- searchParams . get ( "description" ) ?? DEFAULT_DESCRIPTION ,
25- 180 ,
98+ const title = normalizeText ( searchParams . get ( "title" ) , TITLE_MAX_LENGTH ) ;
99+ const description = normalizeText (
100+ searchParams . get ( "description" ) ,
101+ DESCRIPTION_MAX_LENGTH ,
26102 ) ;
103+ const hasText = Boolean ( title || description ) ;
104+ const logoDataUri = await logoDataUriPromise ;
105+ const titleFontSize = title
106+ ? getTitleFontSize ( title . length , Boolean ( description ) )
107+ : 0 ;
108+ const descriptionFontSize = description
109+ ? getDescriptionFontSize ( description . length , Boolean ( title ) )
110+ : 0 ;
27111
28112 return new ImageResponse (
29113 (
@@ -36,6 +120,7 @@ export function GET(request: Request) {
36120 color : "#f5f5f5" ,
37121 position : "relative" ,
38122 fontFamily : "ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif" ,
123+ overflow : "hidden" ,
39124 } }
40125 >
41126 < div
@@ -48,49 +133,154 @@ export function GET(request: Request) {
48133 opacity : 0.25 ,
49134 } }
50135 />
136+ < div
137+ style = { {
138+ position : "absolute" ,
139+ inset : 0 ,
140+ background :
141+ "radial-gradient(90% 70% at 16% 12%, rgba(249,115,22,0.16) 0%, rgba(249,115,22,0) 58%)" ,
142+ } }
143+ />
144+ < div
145+ style = { {
146+ position : "absolute" ,
147+ inset : "38px" ,
148+ border : "1px solid rgba(245,245,245,0.12)" ,
149+ } }
150+ />
51151 < div
52152 style = { {
53153 display : "flex" ,
54154 flexDirection : "column" ,
55- justifyContent : "space-between" ,
56155 width : "100%" ,
57- padding : "56px 64px " ,
156+ height : "100% " ,
58157 position : "relative" ,
158+ padding : "56px 64px" ,
59159 } }
60160 >
61- < div
62- style = { {
63- fontSize : 26 ,
64- letterSpacing : "0.18em" ,
65- textTransform : "uppercase" ,
66- color : "#f97316" ,
67- fontWeight : 700 ,
68- } }
69- >
70- claws.supply
71- </ div >
72- < div style = { { display : "flex" , flexDirection : "column" , gap : 18 , maxWidth : 980 } } >
73- < div
74- style = { {
75- fontSize : title . length > 60 ? 58 : 70 ,
76- lineHeight : 1.08 ,
77- fontWeight : 700 ,
78- letterSpacing : "-0.02em" ,
79- } }
80- >
81- { title }
82- </ div >
83- < div
84- style = { {
85- fontSize : 34 ,
86- lineHeight : 1.25 ,
87- color : "#d4d4d8" ,
88- letterSpacing : "-0.01em" ,
89- } }
90- >
91- { description }
92- </ div >
93- </ div >
161+ { hasText
162+ ? (
163+ < >
164+ { logoDataUri
165+ ? (
166+ < img
167+ src = { logoDataUri }
168+ alt = "Claws.supply logo"
169+ style = { {
170+ position : "absolute" ,
171+ top : 50 ,
172+ left : 56 ,
173+ width : 116 ,
174+ height : 116 ,
175+ objectFit : "contain" ,
176+ border : "1px solid rgba(249,115,22,0.34)" ,
177+ boxShadow : "0 0 0 6px rgba(0,0,0,0.44)" ,
178+ background : "rgba(0,0,0,0.26)" ,
179+ } }
180+ />
181+ )
182+ : (
183+ < div
184+ style = { {
185+ position : "absolute" ,
186+ top : 70 ,
187+ left : 64 ,
188+ fontSize : 22 ,
189+ letterSpacing : "0.12em" ,
190+ textTransform : "uppercase" ,
191+ color : "#f97316" ,
192+ fontWeight : 700 ,
193+ } }
194+ >
195+ claws.supply
196+ </ div >
197+ ) }
198+ < div
199+ style = { {
200+ display : "flex" ,
201+ flexDirection : "column" ,
202+ justifyContent : "flex-end" ,
203+ gap : 16 ,
204+ marginTop : "auto" ,
205+ maxWidth : 1040 ,
206+ paddingBottom : 6 ,
207+ } }
208+ >
209+ { title
210+ ? (
211+ < div
212+ style = { {
213+ fontSize : titleFontSize ,
214+ lineHeight : 1.08 ,
215+ fontWeight : 700 ,
216+ letterSpacing : "-0.03em" ,
217+ color : "#fafafa" ,
218+ wordBreak : "break-word" ,
219+ } }
220+ >
221+ { title }
222+ </ div >
223+ )
224+ : null }
225+ { description
226+ ? (
227+ < div
228+ style = { {
229+ fontSize : descriptionFontSize ,
230+ lineHeight : 1.24 ,
231+ color : "#d6d3d1" ,
232+ letterSpacing : "-0.01em" ,
233+ wordBreak : "break-word" ,
234+ } }
235+ >
236+ { description }
237+ </ div >
238+ )
239+ : null }
240+ </ div >
241+ </ >
242+ )
243+ : (
244+ < div
245+ style = { {
246+ display : "flex" ,
247+ alignItems : "center" ,
248+ justifyContent : "center" ,
249+ width : "100%" ,
250+ height : "100%" ,
251+ } }
252+ >
253+ { logoDataUri
254+ ? (
255+ < img
256+ src = { logoDataUri }
257+ alt = "Claws.supply logo"
258+ style = { {
259+ width : 252 ,
260+ height : 252 ,
261+ objectFit : "contain" ,
262+ border : "1px solid rgba(249,115,22,0.4)" ,
263+ boxShadow :
264+ "0 0 0 10px rgba(0,0,0,0.42), 0 0 56px rgba(249,115,22,0.2)" ,
265+ background : "rgba(0,0,0,0.28)" ,
266+ } }
267+ />
268+ )
269+ : (
270+ < div
271+ style = { {
272+ fontSize : 72 ,
273+ lineHeight : 1.08 ,
274+ fontWeight : 700 ,
275+ letterSpacing : "-0.03em" ,
276+ color : "#fafafa" ,
277+ } }
278+ >
279+ claws.supply
280+ </ div >
281+ ) }
282+ </ div >
283+ ) }
94284 </ div >
95285 </ div >
96286 ) ,
0 commit comments